Initial QSfera import
This commit is contained in:
+4
@@ -0,0 +1,4 @@
|
||||
cmd
|
||||
bench
|
||||
examples
|
||||
tools
|
||||
+1
@@ -0,0 +1 @@
|
||||
import %workspace%/.aspect/bazelrc/bazel7.bazelrc
|
||||
+1
@@ -0,0 +1 @@
|
||||
8.3.1
|
||||
@@ -0,0 +1,39 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# IDE
|
||||
.idea
|
||||
.vscode
|
||||
.DS_Store
|
||||
*~
|
||||
|
||||
coverage.out
|
||||
|
||||
# I redirect my test output to files named "out" way too often
|
||||
out
|
||||
|
||||
cmd/jwx/jwx
|
||||
|
||||
bazel-*
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: all
|
||||
disable:
|
||||
- cyclop
|
||||
- depguard
|
||||
- dupl
|
||||
- err113
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- funcorder
|
||||
- funlen
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gocognit
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godot
|
||||
- godox
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- govet
|
||||
- inamedparam
|
||||
- ireturn
|
||||
- lll
|
||||
- maintidx
|
||||
- makezero
|
||||
- mnd
|
||||
- nakedret
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- nonamedreturns
|
||||
- paralleltest
|
||||
- perfsprint
|
||||
- staticcheck
|
||||
- recvcheck
|
||||
- tagliatelle
|
||||
- testifylint
|
||||
- testpackage
|
||||
- thelper
|
||||
- varnamelen
|
||||
- wrapcheck
|
||||
- wsl
|
||||
- wsl_v5
|
||||
settings:
|
||||
govet:
|
||||
disable:
|
||||
- shadow
|
||||
- fieldalignment
|
||||
enable-all: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
rules:
|
||||
- linters:
|
||||
- staticcheck
|
||||
path: /*.go
|
||||
text: 'ST1003: should not use underscores in package names'
|
||||
- linters:
|
||||
- revive
|
||||
path: /*.go
|
||||
text: don't use an underscore in package name
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: SA1019
|
||||
- linters:
|
||||
- contextcheck
|
||||
- exhaustruct
|
||||
path: /*.go
|
||||
- linters:
|
||||
- errcheck
|
||||
path: /main.go
|
||||
- linters:
|
||||
- errcheck
|
||||
path: internal/codegen/codegen.go
|
||||
- linters:
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- forcetypeassert
|
||||
path: internal/jwxtest/jwxtest.go
|
||||
- linters:
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- forcetypeassert
|
||||
path: /*_test.go
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: /*_example_test.go
|
||||
- linters:
|
||||
- forbidigo
|
||||
path: cmd/jwx/jwx.go
|
||||
- linters:
|
||||
- revive
|
||||
path: /*_test.go
|
||||
text: 'var-naming: '
|
||||
- linters:
|
||||
- revive
|
||||
path: internal/tokens/jwe_tokens.go
|
||||
text: "don't use ALL_CAPS in Go names"
|
||||
- linters:
|
||||
- revive
|
||||
path: jwt/internal/types/
|
||||
text: "var-naming: avoid meaningless package names"
|
||||
- linters:
|
||||
- godoclint
|
||||
path: (^|/)internal/
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofmt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
load("@gazelle//:def.bzl", "gazelle")
|
||||
|
||||
# gazelle:prefix github.com/lestrrat-go/jwx/v3
|
||||
# gazelle:go_naming_convention import_alias
|
||||
|
||||
gazelle(name = "gazelle")
|
||||
|
||||
go_library(
|
||||
name = "jwx",
|
||||
srcs = [
|
||||
"format.go",
|
||||
"formatkind_string_gen.go",
|
||||
"jwx.go",
|
||||
"options.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/json",
|
||||
"//internal/tokens",
|
||||
"@com_github_lestrrat_go_option_v2//:option",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwx_test",
|
||||
srcs = ["jwx_test.go"],
|
||||
deps = [
|
||||
":jwx",
|
||||
"//internal/jose",
|
||||
"//internal/json",
|
||||
"//internal/jwxtest",
|
||||
"//jwa",
|
||||
"//jwe",
|
||||
"//jwk",
|
||||
"//jwk/ecdsa",
|
||||
"//jws",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":jwx",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
Changes
|
||||
=======
|
||||
|
||||
v3 has many incompatibilities with v2. To see the full list of differences between
|
||||
v2 and v3, please read the Changes-v3.md file (https://github.com/lestrrat-go/jwx/blob/develop/v3/Changes-v3.md)
|
||||
|
||||
v3.0.13 12 Jan 2026
|
||||
* [jwt] The `jwt.WithContext()` option is now properly being passed to `jws.Verify()` from
|
||||
`jwt.Parse()`.
|
||||
* [jwx] github.com/lestrrat-go/httprc/v3 has been upgraded to remove dependency on
|
||||
github.com/lestrrat-go/option (v1)
|
||||
* [jwk] `jwk.Clone()` has been fixed to properly work with private fields.
|
||||
|
||||
v3.0.12 20 Oct 2025
|
||||
* [jwe] As part of the next change, now per-recipient headers that are empty
|
||||
are no longer serialized in flattened JSON serialization.
|
||||
|
||||
* [jwe] Introduce `jwe.WithLegacyHeaderMerging(bool)` option to control header
|
||||
merging behavior in during JWE encryption. This only applies to flattened
|
||||
JSON serialization.
|
||||
|
||||
Previously, when using flattened JSON serialization (i.e. you specified
|
||||
JSON serialization via `jwe.WithJSON()` and only supplied one key), per-recipient
|
||||
headers were merged into the protected headers during encryption, and then
|
||||
were left to be included in the final serialization as-is. This caused duplicate
|
||||
headers to be present in both the protected headers and the per-recipient headers.
|
||||
|
||||
Since there may be users who rely on this behavior already, instead of changing the
|
||||
default behavior to fix this duplication, a new option to `jwe.Encrypt()` was added
|
||||
to allow clearing the per-recipient headers after merging to leave the `"headers"`
|
||||
field empty. This in effect makes the flattened JSON serialization more similar to
|
||||
the compact serialization, where there are no per-recipient headers present, and
|
||||
leaves the headers disjoint.
|
||||
|
||||
Note that in compact mode, there are no per-recipient headers and thus the
|
||||
headers need to be merged regardless. In full JSON serialization, we never
|
||||
merge the headers, so it is left up to the user to keep the headers disjoint.
|
||||
|
||||
* [jws] Calling the deprecated `jws.NewSigner()` function for the first time will cause
|
||||
legacy signers to be loaded automatically. Previously, you had to explicitly
|
||||
call `jws.Settings(jws.WithLegacySigners(true))` to enable legacy signers.
|
||||
|
||||
We incorrectly assumed that users would not be using `jws.NewSigner()`, and thus
|
||||
disabled legacy signers by default. However, it turned out that some users
|
||||
were using `jws.NewSigner()` in their code, which lead to breakages in
|
||||
existing code. In hindsight we should have known that any API made public before will
|
||||
be used by _somebody_.
|
||||
|
||||
As a side effect, jws.Settings(jws.WithLegacySigners(...)) is now a no-op.
|
||||
|
||||
However, please do note that jws.Signer (and similar) objects were always intended to be
|
||||
used for _registering_ new signing/verifying algorithms, and not for end users to actually
|
||||
use them directly. If you are using them for other purposes, please consider changing
|
||||
your code, as it is more than likely that we will somehow deprecate/remove/discouraged
|
||||
their use in the future.
|
||||
|
||||
v3.0.11 14 Sep 2025
|
||||
* [jwk] Add `(jwk.Cache).Shutdown()` method that delegates to the httprc controller
|
||||
object, to shutdown the cache.
|
||||
* [jwk] Change timing of `res.Body.Close()` call
|
||||
* [jwe] Previously, ecdh.PrivateKey/ecdh.PublicKey were not properly handled
|
||||
when used for encryption, which has been fixed.
|
||||
* [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Convert most functions into
|
||||
thin wrappers around functions from github.com/lestrrat-go/dsig package.
|
||||
As a related change, HAMCHashFuncFor/RSAHashFuncFor/ECDSAHashFuncFor/RSAPSSOptions
|
||||
have been removed or unexported.
|
||||
Users of this module should be using jwsbb.Sign() and jwsbb.Verify() instead of
|
||||
algorithm specific jwsbb.SignRSA()/jwsbb.VerifyRSA() and such. If you feel the
|
||||
need to use these functions, you should use github.com/lestrrat-go/dsig directly.
|
||||
|
||||
v3.0.10 04 Aug 2025
|
||||
* [jws/jwsbb] Add `jwsbb.ErrHeaderNotFound()` to return the same error type as when
|
||||
a non-existent header is requested. via `HeaderGetXXX()` functions. Previously, this
|
||||
function was called `jwsbb.ErrFieldNotFound()`, but it was a misnomer.
|
||||
* [jws/jwsbb] Fix a bug where error return values from `HeaderGetXXX()` functions
|
||||
could not be matched against `jwsbb.ErrHeaderNotFound()` using `errors.Is()`.
|
||||
|
||||
v3.0.9 31 Jul 2025
|
||||
* [jws/jwsbb] `HeaderGetXXX()` functions now return errors when
|
||||
the requested header is not found, or if the value cannot be
|
||||
converted to the requested type.
|
||||
|
||||
* [jwt] `(jwt.Token).Get` methods now return specific types of errors depending
|
||||
on if a) the specified claim was not present, or b) the specified claim could
|
||||
not be assigned to the destination variable.
|
||||
|
||||
You can distinguish these by using `errors.Is` against `jwt.ClaimNotFoundError()`
|
||||
or `jwt.ClaimAssignmentFailedError()`
|
||||
|
||||
v3.0.8 27 Jun 2025
|
||||
* [jwe/jwebb] (EXPERIMENTAL) Add low-level functions for JWE operations.
|
||||
* [jws/jwsbb] (EXPERIMENTAL/BREAKS COMPATIBILITY) Add io.Reader parameter
|
||||
so your choice of source of randomness can be passed. Defaults to crypto/rand.Reader.
|
||||
Function signatures around jwsbb.Sign() now accept an addition `rr io.Reader`,
|
||||
which can be nil for 99% of use cases.
|
||||
* [jws/jwsbb] Add HeaderParse([]byte), where it is expected that the header
|
||||
is already in its base64 decoded format.
|
||||
* misc: replace `interface{}` with `any`
|
||||
|
||||
v3.0.7 16 Jun 2025
|
||||
* [jws/jwsbb] (EXPERIMENTAL) Add low-level fast access to JWS headers in compact
|
||||
serialization form.
|
||||
* [jws] Fix error reporting when no key matched for a signature.
|
||||
* [jws] Refactor jws signer setup.
|
||||
* Known algorithms are now implemented completely in the jws/jwsbb package.
|
||||
* VerifierFor and SignerFor now always succeed, and will also return a Signer2
|
||||
or Verifier2 that wraps the legacy Signer or Verifier if one is registered.
|
||||
|
||||
v3.0.6 13 Jun 2025
|
||||
* This release contains various performance improvements all over the code.
|
||||
No, this time for real. In particular, the most common case for signing
|
||||
a JWT with a key is approx 70% more efficient based on the number of allocations.
|
||||
|
||||
Please read the entry for the (retracted) v3.0.4 for what else I have to
|
||||
say about performance improvements
|
||||
|
||||
* [jwt] Added fast-path for token signing and verification. The fast path
|
||||
is triggered if you only pass `jwt.Sign()` and `jwt.Parse()` one options each
|
||||
(`jwt.WithKey()`), with no suboptions.
|
||||
|
||||
* [jws] Major refactoring around basic operations:
|
||||
|
||||
* How to work with Signer/Verifier have completely changed. Please take
|
||||
a look at examples/jws_custom_signer_verifier_example_test.go for how
|
||||
to do it the new way. The old way still works, but it WILL be removed
|
||||
when v4 arrives.
|
||||
* Related to the above, old code has been moved to `jws/legacy`.
|
||||
|
||||
* A new package `jws/jwsbb` has been added. `bb` stands for building blocks.
|
||||
This package separates out the low-level JWS operations into its own
|
||||
package. So if you are looking for just the signing of a payload with
|
||||
a key, this is it.
|
||||
|
||||
`jws/jwsbb` is currently considered to be EXPERIMENTAL.
|
||||
|
||||
v3.0.5 11 Jun 2025
|
||||
* Retract v3.0.4
|
||||
* Code for v3.0.3 is the same as v3.0.3
|
||||
|
||||
v3.0.4 09 Jun 2025
|
||||
* This release contains various performance improvements all over the code.
|
||||
|
||||
Because of the direction that this library is taking, we have always been
|
||||
more focused on correctness and usability/flexibility over performance.
|
||||
|
||||
It just so happens that I had a moment of inspiration and decided to see
|
||||
just how good our AI-based coding agents are in this sort of analysis-heavy tasks.
|
||||
|
||||
Long story short, the AI was fairly good at identifying suspicious code with
|
||||
an okay accuracy, but completely failed to make any meaningful changes to the
|
||||
code in a way that both did not break the code _and_ improved performance.
|
||||
I am sure that they will get better in the near future, but for now,
|
||||
I had to do the changes myself. I should clarify to their defence that
|
||||
the AI was very helpful in writing cumbersome benchmark code for me.
|
||||
|
||||
The end result is that we have anywhere from 10 to 30% performance improvements
|
||||
in various parts of the code that we touched, based on number of allocations.
|
||||
We believe that this would be a significant improvement for many users.
|
||||
|
||||
For further improvements, we can see that there would be a clear benefit to
|
||||
writing optimized code path that is designed to serve the most common cases.
|
||||
For example, for the case of signing JWTs with a single key, we could provide
|
||||
a path that skips a lot of extra processing (we kind of did that in this change,
|
||||
but we _could_ go ever harder in this direction). However, it is a trade-off between
|
||||
maintainability and performance, and as I am currently the sole maintainer of
|
||||
this library for the time being, I only plan to pursue such a route where it
|
||||
requires minimal effort on my part.
|
||||
|
||||
If you are interested in helping out in this area, I hereby thank you in advance.
|
||||
However, please be perfectly clear that unlike other types of changes, for performance
|
||||
related changes, the balance between the performance gains and maintainability is
|
||||
top priority. If you have good ideas and code, they will always be welcome, but
|
||||
please be prepared to justify your changes.
|
||||
|
||||
Finally, thank you for using this library!
|
||||
|
||||
v3.0.3 06 Jun 2025
|
||||
* Update some dependencies
|
||||
* [jwe] Change some error messages to contain more context information
|
||||
|
||||
v3.0.2 03 Jun 2025
|
||||
* [transform] (EXPERIMENTAL) Add utility function `transform.AsMap` to convert a
|
||||
Mappable object to a map[string]interface{}. This is useful for converting
|
||||
objects such as `jws.Header`, `jwk.Key`, `jwt.Token`, etc. to a map that can
|
||||
be used with other libraries that expect a map.
|
||||
* [jwt] (EXPERIMENTAL) Added token filtering functionality through the TokenFilter interface.
|
||||
* [jwt/openid] (EXPERIMENTAL) Added StandardClaimsFilter() for filtering standard OpenID claims.
|
||||
* [jws] (EXPERIMENTAL) Added header filtering functionality through the HeaderFilter interface.
|
||||
* [jwe] (EXPERIMENTAL) Added header filtering functionality through the HeaderFilter interface.
|
||||
* [jwk] (EXPERIMENTAL) Added key filtering functionality through the KeyFilter interface.
|
||||
* [jwk] `jwk.Export` previously did not recognize third-party objects that implemented `jwk.Key`,
|
||||
as it was detecting what to do by checking if the object was one of our own unexported
|
||||
types. This caused some problems for consumers of this library that wanted to extend the
|
||||
features of the keys.
|
||||
|
||||
Now `jwk.Export` checks types against interface types such as `jwk.RSAPrivateKey`, `jwk.ECDSAPrivateKey`, etc.
|
||||
It also uses some reflect blackmagic to detect if the given object implements the `jwk.Key` interface
|
||||
via embedding, so you should be able to embed a `jwk.Key` to another object to act as if it
|
||||
is a legitimate `jwk.Key`, as far as `jwk.Export` is concerned.
|
||||
|
||||
v3.0.1 29 Apr 2025
|
||||
* [jwe] Fixed a long standing bug that could lead to degraded encryption or failure to
|
||||
decrypt JWE messages when a very specific combination of inputs were used for
|
||||
JWE operations.
|
||||
|
||||
This problem only manifested itself when the following conditions in content encryption or decryption
|
||||
were met:
|
||||
- Content encryption was specified to use DIRECT mode.
|
||||
- Contentn encryption algorithm is specified as A256CBC_HS512
|
||||
- The key was erronously constructed with a 32-byte content encryption key (CEK)
|
||||
|
||||
In this case, the user would be passing a mis-constructed key of 32-bytes instead
|
||||
of the intended 64-bytes. In all other cases, this construction would cause
|
||||
an error because `crypto/aes.NewCipher` would return an error when a key with length
|
||||
not matching 16, 24, and 32 bytes is used. However, due to use using a the provided
|
||||
32-bytes as half CEK and half the hash, the `crypto/aes.NewCipher` was passed
|
||||
a 16-byte key, which is fine for AES-128. So internally `crypto/aes.NewCipher` would
|
||||
choose to use AES-128 instead of AES-256, and happily continue. Note that no other
|
||||
key lengths such as 48 and 128 would have worked. It had to be exactly 32.
|
||||
|
||||
This does indeed result in a downgraded encryption, but we believe it is unlikely that this would cause a problem in the real world,
|
||||
as you would have to very specifically choose to use DIRECT mode, choose
|
||||
the specific content encryption algorithm, AND also use the wrong key size of
|
||||
exactly 32 bytes.
|
||||
|
||||
However, in abandunce of caution, we recommend that you upgrade to v3.0.1 or later,
|
||||
or v2.1.6 or later if you are still on v2 series.
|
||||
|
||||
* [jws] Improve performance of jws.SplitCompact and jws.SplitCompactString
|
||||
* [jwe] Improve performance of jwe.Parse
|
||||
|
||||
v3.0.0 1 Apr 2025
|
||||
* Release initial v3.0.0 series. Code is identical to v3.0.0-beta2, except
|
||||
for minor documentation changes.
|
||||
|
||||
Please note that v1 will no longer be maintained.
|
||||
|
||||
Going forward v2 will receive security updates but will no longer receive
|
||||
feature updates. Users are encouraged to migrate to v3. There is no hard-set
|
||||
guarantee as to how long v2 will be supported, but if/when v4 comes out,
|
||||
v2 support will be terminated then.
|
||||
|
||||
v3.0.0-beta2 30 Mar 2025
|
||||
* [jwk] Fix a bug where `jwk.Set`'s `Keys()` method did not return the proper
|
||||
non-standard fields. (#1322)
|
||||
* [jws][jwt] Implement `WithBase64Encoder()` options to pass base64 encoders
|
||||
to use during signing/verifying signatures. This useful when the token
|
||||
provider generates JWTs that don't follow the specification and uses base64
|
||||
encoding other than raw url encoding (no padding), such as, apparently,
|
||||
AWS ALB. (#1324, #1328)
|
||||
|
||||
v3.0.0-beta1 15 Mar 2025
|
||||
* [jwt] Token validation no longer truncates time based fields by default.
|
||||
To restore old behavior, you can either change the global settings by
|
||||
calling `jwt.Settings(jwt.WithTruncation(time.Second))`, or you can
|
||||
change it by each invocation by using `jwt.Validate(..., jwt.WithTruncation(time.Second))`
|
||||
|
||||
v3.0.0-alpha3 13 Mar 2025
|
||||
* [jwk] Importing/Exporting from jwk.Key with P256/P386/P521 curves to
|
||||
ecdh.PrivateKey/ecdh.PublicKey should now work. Previously these keys were not properly
|
||||
recognized by the exporter/importer. Note that keys that use X25519 and P256/P384/P521
|
||||
behave differently: X25519 keys can only be exported to/imported from OKP keys,
|
||||
while P256/P384/P521 can be exported to either ecdsa or ecdh keys.
|
||||
|
||||
v3.0.0-alpha2 25 Feb 2025
|
||||
* Update to work with go1.24
|
||||
* Update tests to work with latest latchset/jose
|
||||
* Fix build pipeline to work with latest golangci-lint
|
||||
* Require go1.23
|
||||
|
||||
v3.0.0-alpha1 01 Nov 2024
|
||||
* Initial release of v3 line.
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
# Incompatible Changes from v1 to v2
|
||||
|
||||
These are changes that are incompatible with the v1.x.x version.
|
||||
|
||||
* [tl;dr](#tldr) - If you don't feel like reading the details -- but you will read the details, right?
|
||||
* [Detailed List of Changes](#detailed-list-of-changes) - A comprehensive list of changes from v1 to v2
|
||||
|
||||
# tl;dr
|
||||
|
||||
## JWT
|
||||
|
||||
```go
|
||||
// most basic
|
||||
jwt.Parse(serialized, jwt.WithKey(alg, key)) // NOTE: verification and validation are ENABLED by default!
|
||||
jwt.Sign(token, jwt.WithKey(alg,key))
|
||||
|
||||
// with a jwk.Set
|
||||
jwt.Parse(serialized, jwt.WithKeySet(set))
|
||||
|
||||
// UseDefault/InferAlgorithm with JWKS
|
||||
jwt.Parse(serialized, jwt.WithKeySet(set,
|
||||
jws.WithUseDefault(true), jws.WithInferAlgorithm(true))
|
||||
|
||||
// Use `jku`
|
||||
jwt.Parse(serialized, jwt.WithVerifyAuto(...))
|
||||
|
||||
// Any other custom key provisioning (using functions in this
|
||||
// example, but can be anything that fulfills jws.KeyProvider)
|
||||
jwt.Parse(serialized, jwt.WithKeyProvider(jws.KeyProviderFunc(...)))
|
||||
```
|
||||
|
||||
## JWK
|
||||
|
||||
```go
|
||||
// jwk.New() was confusing. Renamed to fit the actual implementation
|
||||
key, err := jwk.FromRaw(rawKey)
|
||||
|
||||
// Algorithm() now returns jwa.KeyAlgorithm type. `jws.Sign()`
|
||||
// and other function that receive JWK algorithm names accept
|
||||
// this new type, so you can use the same key and do the following
|
||||
// (previously you needed to type assert)
|
||||
jws.Sign(payload, jws.WithKey(key.Algorithm(), key))
|
||||
|
||||
// If you need the specific type, type assert
|
||||
key.Algorithm().(jwa.SignatureAlgorithm)
|
||||
|
||||
// jwk.AutoRefresh is no more. Use jwk.Cache
|
||||
cache := jwk.NewCache(ctx, options...)
|
||||
|
||||
// Certificate chains are no longer jwk.CertificateChain type, but
|
||||
// *(github.com/lestrrat-go/jwx/cert).Chain
|
||||
cc := key.X509CertChain() // this is *cert.Chain now
|
||||
```
|
||||
|
||||
## JWS
|
||||
|
||||
```go
|
||||
// basic
|
||||
jws.Sign(payload, jws.WithKey(alg, key))
|
||||
jws.Sign(payload, jws.WithKey(alg, key), jws.WithKey(alg, key), jws.WithJSON(true))
|
||||
jws.Verify(signed, jws.WithKey(alg, key))
|
||||
|
||||
// other ways to pass the key
|
||||
jws.Sign(payload, jws.WithKeySet(jwks))
|
||||
jws.Sign(payload, jws.WithKeyProvider(kp))
|
||||
|
||||
// retrieve the key that succeeded in verifying
|
||||
var keyUsed interface{}
|
||||
jws.Verify(signed, jws.WithKeySet(jwks), jws.WithKeyUsed(&keyUsed))
|
||||
```
|
||||
|
||||
## JWE
|
||||
|
||||
```go
|
||||
// basic
|
||||
jwe.Encrypt(payload, jwe.WithKey(alg, key)) // other defaults are inferred
|
||||
jwe.Encrypt(payload, jwe.WithKey(alg, key), jwe.WithKey(alg, key), jwe.WithJSON(true))
|
||||
jwe.Decrypt(encrypted, jwe.WithKey(alg, key))
|
||||
|
||||
// other ways to pass the key
|
||||
jwe.Encrypt(payload, jwe.WithKeySet(jwks))
|
||||
jwe.Encrypt(payload, jwe.WithKeyProvider(kp))
|
||||
|
||||
// retrieve the key that succeeded in decrypting
|
||||
var keyUsed interface{}
|
||||
jwe.Verify(signed, jwe.WithKeySet(jwks), jwe.WithKeyUsed(&keyUsed))
|
||||
```
|
||||
|
||||
# Detailed List of Changes
|
||||
|
||||
## Module
|
||||
|
||||
* Module now requires go 1.16
|
||||
|
||||
* Use of github.com/pkg/errors is no more. If you were relying on behavior
|
||||
that depends on the errors being an instance of github.com/pkg/errors
|
||||
then you need to change your code
|
||||
|
||||
* File-generation tools have been moved out of internal/ directories.
|
||||
These files pre-dates Go modules, and they were in internal/ in order
|
||||
to avoid being listed in the `go doc` -- however, now that we can
|
||||
make them separate modules this is no longer necessary.
|
||||
|
||||
* New package `cert` has been added to handle `x5c` certificate
|
||||
chains, and to work with certificates
|
||||
* cert.Chain to store base64 encoded ASN.1 DER format certificates
|
||||
* cert.EncodeBase64 to encode ASN.1 DER format certificate using base64
|
||||
* cert.Create to create a base64 encoded ASN.1 DER format certificates
|
||||
* cert.Parse to parse base64 encoded ASN.1 DER format certificates
|
||||
|
||||
## JWE
|
||||
|
||||
* `jwe.Compact()`'s signature has changed to
|
||||
`jwe.Compact(*jwe.Message, ...jwe.CompactOption)`
|
||||
|
||||
* `jwe.JSON()` has been removed. You can generate JSON serialization
|
||||
using `jwe.Encrypt(jwe.WitJSON())` or `json.Marshal(jwe.Message)`
|
||||
|
||||
* `(jwe.Message).Decrypt()` has been removed. Since formatting of the
|
||||
original serialized message matters (including whitespace), using a parsed
|
||||
object was inherently confusing.
|
||||
|
||||
* `jwe.Encrypt()` can now generate JWE messages in either compact or JSON
|
||||
forms. By default, the compact form is used. JSON format can be
|
||||
enabled by using the `jwe.WithJSON` option.
|
||||
|
||||
* `jwe.Encrypt()` can now accept multiple keys by passing multiple
|
||||
`jwe.WithKey()` options. This can be used with `jwe.WithJSON` to
|
||||
create JWE messages with multiple recipients.
|
||||
|
||||
* `jwe.DecryptEncryptOption()` has been renamed to `jwe.EncryptDecryptOption()`.
|
||||
This is so that it is more uniform with `jws` equivalent of `jws.SignVerifyOption()`
|
||||
where the producer (`Sign`) comes before the consumer (`Verify`) in the naming
|
||||
|
||||
* `jwe.WithCompact` and `jwe.WithJSON` options have been added
|
||||
to control the serialization format.
|
||||
|
||||
* jwe.Decrypt()'s method signature has been changed to `jwt.Decrypt([]byte, ...jwe.DecryptOption) ([]byte, error)`.
|
||||
These options can be stacked. Therefore, you could configure the
|
||||
verification process to attempt a static key pair, a JWKS, and only
|
||||
try other forms if the first two fails, for example.
|
||||
|
||||
- For static key pair, use `jwe.WithKey()`
|
||||
- For static JWKS, use `jwe.WithKeySet()` (NOTE: InferAlgorithmFromKey like in `jws` package is NOT supported)
|
||||
- For custom, possibly dynamic key provisioning, use `jwe.WithKeyProvider()`
|
||||
|
||||
* jwe.Decrypter has been unexported. Users did not need this.
|
||||
|
||||
* jwe.WithKeyProvider() has been added to specify arbitrary
|
||||
code to specify which keys to try.
|
||||
|
||||
* jwe.KeyProvider interface has been added
|
||||
|
||||
* jwe.KeyProviderFunc has been added
|
||||
|
||||
* `WithPostParser()` has been removed. You can achieve the same effect
|
||||
by using `jwe.WithKeyProvider()`. Because this was the only consumer for
|
||||
`jwe.DecryptCtx`, this type has been removed as well.
|
||||
|
||||
* `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
|
||||
|
||||
* Method signature for `jwe.Parse()` has been changed to include options,
|
||||
but options are currently not used
|
||||
|
||||
* `jwe.ReadFile` now supports the option `jwe.WithFS` which allows you to
|
||||
read data from arbitrary `fs.FS` objects
|
||||
|
||||
* jwe.WithKeyUsed has been added to allow users to retrieve
|
||||
the key used for decryption. This is useful in cases you provided
|
||||
multiple keys and you want to know which one was successful
|
||||
|
||||
## JWK
|
||||
|
||||
* `jwk.New()` has been renamed to `jwk.FromRaw()`, which hopefully will
|
||||
make it easier for the users what the input should be.
|
||||
|
||||
* `jwk.Set` has many interface changes:
|
||||
* Changed methods to match jwk.Key and its semantics:
|
||||
* Field is now Get() (returns values for arbitrary fields other than keys). Fetching a key is done via Key()
|
||||
* Remove() now removes arbitrary fields, not keys. to remove keys, use RemoveKey()
|
||||
* Iterate has been added to iterate through all non-key fields.
|
||||
* Add is now AddKey(Key) string, and returns an error when the same key is added
|
||||
* Get is now Key(int) (Key, bool)
|
||||
* Remove is now RemoveKey(Key) error
|
||||
* Iterate is now Keys(context.Context) KeyIterator
|
||||
* Clear is now Clear() error
|
||||
|
||||
* `jwk.CachedSet` has been added. You can create a `jwk.Set` that is backed by
|
||||
`jwk.Cache` so you can do this:
|
||||
|
||||
```go
|
||||
cache := jkw.NewCache(ctx)
|
||||
cachedSet := jwk.NewCachedSet(cache, jwksURI)
|
||||
|
||||
// cachedSet is always the refreshed, cached version from jwk.Cache
|
||||
jws.Verify(signed, jws.WithKeySet(cachedSet))
|
||||
```
|
||||
|
||||
* `jwk.NewRSAPRivateKey()`, `jwk.NewECDSAPrivateKey()`, etc have been removed.
|
||||
There is no longer any way to create concrete types of `jwk.Key`
|
||||
|
||||
* `jwk.Key` type no longer supports direct unmarshaling via `json.Unmarshal()`,
|
||||
because you can no longer instantiate concrete `jwk.Key` types. You will need to
|
||||
use `jwk.ParseKey()`. See the documentation for ways to parse JWKs.
|
||||
|
||||
* `(jwk.Key).Algorithm()` is now of `jwk.KeyAlgorithm` type. This field used
|
||||
to be `string` and therefore could not be passed directly to `jwt.Sign()`
|
||||
`jws.Sign()`, `jwe.Encrypt()`, et al. This is no longer the case, and
|
||||
now you can pass it directly. See
|
||||
https://github.com/lestrrat-go/jwx/blob/v2/docs/99-faq.md#why-is-jwkkeyalgorithm-and-jwakeyalgorithm-so-confusing
|
||||
for more details
|
||||
|
||||
* `jwk.Fetcher` and `jwk.FetchFunc` has been added.
|
||||
They represent something that can fetch a `jwk.Set`
|
||||
|
||||
* `jwk.CertificateChain` has been removed, use `*cert.Chain`
|
||||
* `x5c` field type has been changed to `*cert.Chain` instead of `[]*x509.Certificate`
|
||||
|
||||
* `jwk.ReadFile` now supports the option `jwk.WithFS` which allows you to
|
||||
read data from arbitrary `fs.FS` objects
|
||||
|
||||
* Added `jwk.PostFetcher`, `jwk.PostFetchFunc`, and `jwk.WithPostFetch` to
|
||||
allow users to get at the `jwk.Set` that was fetched in `jwk.Cache`.
|
||||
This will make it possible for users to supply extra information and edit
|
||||
`jwk.Set` after it has been fetched and parsed, but before it is cached.
|
||||
You could, for example, modify the `alg` field so that it's easier to
|
||||
work with when you use it in `jws.Verify` later.
|
||||
|
||||
* Reworked `jwk.AutoRefresh` in terms of `github.com/lestrrat-go/httprc`
|
||||
and renamed it `jwk.Cache`.
|
||||
|
||||
Major difference between `jwk.AutoRefresh` and `jwk.Cache` is that while
|
||||
former used one `time.Timer` per resource, the latter uses a static timer
|
||||
(based on `jwk.WithRefreshWindow()` value, default 15 minutes) that periodically
|
||||
refreshes all resources that were due to be refreshed within that time frame.
|
||||
|
||||
This method may cause your updates to happen slightly later, but uses significantly
|
||||
less resources and is less prone to clogging.
|
||||
|
||||
* Reimplemented `jwk.Fetch` in terms of `github.com/lestrrat-go/httprc`.
|
||||
|
||||
* Previously `jwk.Fetch` and `jwk.AutoRefresh` respected backoff options,
|
||||
but this has been removed. This is to avoid unwanted clogging of the fetch workers
|
||||
which is the default processing mode in `github.com/lestrrat-go/httprc`.
|
||||
|
||||
If you are using backoffs, you need to control your inputs more carefully so as
|
||||
not to clog your fetch queue, and therefore you should be writing custom code that
|
||||
suits your needs
|
||||
|
||||
## JWS
|
||||
|
||||
* `jws.Sign()` can now generate JWS messages in either compact or JSON
|
||||
forms. By default, the compact form is used. JSON format can be
|
||||
enabled by using the `jws.WithJSON` option.
|
||||
|
||||
* `jws.Sign()` can now accept multiple keys by passing multiple
|
||||
`jws.WithKey()` options. This can be used with `jws.WithJSON` to
|
||||
create JWS messages with multiple signatures.
|
||||
|
||||
* `jws.WithCompact` and `jws.WithJSON` options have been added
|
||||
to control the serialization format.
|
||||
|
||||
* jws.Verify()'s method signature has been changed to `jwt.Verify([]byte, ...jws.VerifyOption) ([]byte, error)`.
|
||||
These options can be stacked. Therefore, you could configure the
|
||||
verification process to attempt a static key pair, a JWKS, and only
|
||||
try other forms if the first two fails, for example.
|
||||
|
||||
- For static key pair, use `jws.WithKey()`
|
||||
- For static JWKS, use `jws.WithKeySet()`
|
||||
- For enabling verification using `jku`, use `jws.WithVerifyAuto()`
|
||||
- For custom, possibly dynamic key provisioning, use `jws.WithKeyProvider()`
|
||||
|
||||
* jws.WithVerify() has been removed.
|
||||
|
||||
* jws.WithKey() has been added to specify an algorithm + key to
|
||||
verify the payload with.
|
||||
|
||||
* jws.WithKeySet() has been added to specify a JWKS to be used for
|
||||
verification. By default `kid` AND `alg` must match between the signature
|
||||
and the key.
|
||||
|
||||
The option can take further suboptions:
|
||||
|
||||
```go
|
||||
jws.Parse(serialized,
|
||||
jws.WithKeySet(set,
|
||||
// by default `kid` is required. set false to disable.
|
||||
jws.WithRequireKid(false),
|
||||
// optionally skip matching kid if there's exactly one key in set
|
||||
jws.WithUseDefault(true),
|
||||
// infer algorithm name from key type
|
||||
jws.WithInferAlgorithm(true),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
* `jws.VerifuAuto` has been removed in favor of using
|
||||
`jws.WithVerifyAuto` option with `jws.Verify()`
|
||||
|
||||
* `jws.WithVerifyAuto` has been added to enable verification
|
||||
using `jku`.
|
||||
|
||||
The first argument must be a jwk.Fetcher object, but can be
|
||||
set to `nil` to use the default implementation which is `jwk.Fetch`
|
||||
|
||||
The rest of the arguments are treated as options passed to the
|
||||
`(jwk.Fetcher).Fetch()` function.
|
||||
|
||||
* Remove `jws.WithPayloadSigner()`. This should be completely replaceable
|
||||
using `jws.WithKey()`
|
||||
|
||||
* jws.WithKeyProvider() has been added to specify arbitrary
|
||||
code to specify which keys to try.
|
||||
|
||||
* jws.KeyProvider interface has been added
|
||||
|
||||
* jws.KeyProviderFunc has been added
|
||||
|
||||
* jws.WithKeyUsed has been added to allow users to retrieve
|
||||
the key used for verification. This is useful in cases you provided
|
||||
multiple keys and you want to know which one was successful
|
||||
|
||||
* `x5c` field type has been changed to `*cert.Chain` instead of `[]string`
|
||||
|
||||
* `jws.ReadFile` now supports the option `jws.WithFS` which allows you to
|
||||
read data from arbitrary `fs.FS` objects
|
||||
|
||||
## JWT
|
||||
|
||||
* `jwt.Parse` now verifies the signature and validates the token
|
||||
by default. You must disable it explicitly using `jwt.WithValidate(false)`
|
||||
and/or `jwt.WithVerify(false)` if you only want to parse the JWT message.
|
||||
|
||||
If you don't want either, a convenience function `jwt.ParseInsecure`
|
||||
has been added.
|
||||
|
||||
* `jwt.Parse` can only parse raw JWT (JSON) or JWS (JSON or Compact).
|
||||
It no longer accepts JWE messages.
|
||||
|
||||
* `jwt.WithDecrypt` has been removed
|
||||
|
||||
* `jwt.WithJweHeaders` has been removed
|
||||
|
||||
* `jwt.WithVerify()` has been renamed to `jwt.WithKey()`. The option can
|
||||
be used for signing, encryption, and parsing.
|
||||
|
||||
* `jwt.Validator` has been changed to return `jwt.ValidationError`.
|
||||
If you provide a custom validator, you should wrap the error with
|
||||
`jwt.NewValidationError()`
|
||||
|
||||
* `jwt.UseDefault()` has been removed. You should use `jws.WithUseDefault()`
|
||||
as a suboption in the `jwt.WithKeySet()` option.
|
||||
|
||||
```go
|
||||
jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithUseDefault(true)))
|
||||
```
|
||||
|
||||
* `jwt.InferAlgorithmFromKey()` has been removed. You should use
|
||||
`jws.WithInferAlgorithmFromKey()` as a suboption in the `jwt.WithKeySet()` option.
|
||||
|
||||
```go
|
||||
jwt.Parse(serialized, jwt.WithKeySet(set, jws.WithInferAlgorithmFromKey(true)))
|
||||
```
|
||||
|
||||
* jwt.WithKeySetProvider has been removed. Use `jwt.WithKeyProvider()`
|
||||
instead. If jwt.WithKeyProvider seems a bit complicated, use a combination of
|
||||
JWS parse, no-verify/validate JWT parse, and an extra JWS verify:
|
||||
|
||||
```go
|
||||
msg, _ := jws.Parse(signed)
|
||||
token, _ := jwt.Parse(msg.Payload(), jwt.WithVerify(false), jwt.WithValidate(false))
|
||||
// Get information out of token, for example, `iss`
|
||||
switch token.Issuer() {
|
||||
case ...:
|
||||
jws.Verify(signed, jwt.WithKey(...))
|
||||
}
|
||||
```
|
||||
|
||||
* `jwt.WithHeaders` and `jwt.WithJwsHeaders` have been removed.
|
||||
You should be able to use the new `jwt.WithKey` option to pass headers
|
||||
|
||||
* `jwt.WithSignOption` and `jwt.WithEncryptOption` have been added as
|
||||
escape hatches for options that are declared in `jws` and `jwe` packages
|
||||
but not in `jwt`
|
||||
|
||||
* `jwt.ReadFile` now supports the option `jwt.WithFS` which allows you to
|
||||
read data from arbitrary `fs.FS` objects
|
||||
|
||||
* `jwt.Sign()` has been changed so that it works more like the new `jws.Sign()`
|
||||
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
# Incompatible Changes from v2 to v3
|
||||
|
||||
These are changes that are incompatible with the v2.x.x version.
|
||||
|
||||
# Detailed list of changes
|
||||
|
||||
## Module
|
||||
|
||||
* This module now requires Go 1.23
|
||||
|
||||
* All `xxx.Get()` methods have been changed from `Get(string) (interface{}, error)` to
|
||||
`Get(string, interface{}) error`, where the second argument should be a pointer
|
||||
to the storage destination of the field.
|
||||
|
||||
* All convenience accessors (e.g. `(jwt.Token).Subject`) now return `(T, bool)` instead of
|
||||
`T`. If you want an accessor that returns a single value, consider using `Get()`
|
||||
|
||||
* Most major errors can now be differentiated using `errors.Is`
|
||||
|
||||
## JWA
|
||||
|
||||
* All string constants have been renamed to equivalent functions that return a struct.
|
||||
You should rewrite `jwa.RS256` as `jwa.RS256()` and so forth.
|
||||
|
||||
* By default, only known algorithm names are accepted. For example, in our JWK tests,
|
||||
there are tests that deal with "ECMR" algorithm, but this will now fail by default.
|
||||
If you want this algorithm to succeed parsing, you need to call `jwa.RegisterXXXX`
|
||||
functions before using them.
|
||||
|
||||
* Previously, unmarshaling unquoted strings used to work (e.g. `var s = "RS256"`),
|
||||
but now they must conform to the JSON standard and be quoted (e.g. `var s = strconv.Quote("RS256")`)
|
||||
|
||||
## JWT
|
||||
|
||||
* All convenience accessors (e.g. `Subject`) now return `(T, bool)` instead of
|
||||
just `T`. If you want a single return value accessor, use `Get(dst) error` instead.
|
||||
|
||||
* Validation used to work for `iat`, `nbf`, `exp` fields where these fields were
|
||||
set to the explicit time.Time{} zero value, but now the _presence_ of these fields matter.
|
||||
|
||||
* Validation of fields related to time used to be truncated to one second accuracy,
|
||||
but no longer does so. To restore old behavior, you can either change the global settings by
|
||||
calling `jwt.Settings(jwt.WithTruncation(time.Second))`, or you can
|
||||
change it by each invocation by using `jwt.Validate(..., jwt.WithTruncation(time.Second))`
|
||||
|
||||
* Error names have been renamed. For example `jwt.ErrInvalidJWT` has been renamed to
|
||||
`jwt.UnknownPayloadTypeError` to better reflect what the error means. For other errors,
|
||||
`func ErrXXXX()` have generally been renamed to `func XXXError()`
|
||||
|
||||
* Validation errors are now wrapped. While `Validate()` returns a `ValidateError()` type,
|
||||
it can also be matched against more specific error types such as `TokenExpierdError()`
|
||||
using `errors.Is`
|
||||
|
||||
* `jwt.ErrMissingRequiredClaim` has been removed
|
||||
|
||||
## JWS
|
||||
|
||||
* Iterators have been completely removed.
|
||||
* As a side effect of removing iterators, some methods such as `Copy()` lost the
|
||||
`context.Context` argument
|
||||
|
||||
* All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of
|
||||
just `T`. If you want a single return value accessor, use `Get(dst) error` instead.
|
||||
|
||||
* Errors from `jws.Sign` and `jws.Verify`, as well as `jws.Parse` (and friends)
|
||||
can now be differentiated by using `errors.Is`. All `jws.IsXXXXError` functions
|
||||
have been removed.
|
||||
|
||||
## JWE
|
||||
|
||||
* Iterators have been completely removed.
|
||||
* As a side effect of removing iterators, some methods such as `Copy()` lost the
|
||||
`context.Context` argument
|
||||
|
||||
* All convenience accessors (e.g. `Algorithm`) now return `(T, bool)` instead of
|
||||
just `T`. If you want a single return value accessor, use `Get(dst) error` instead.
|
||||
|
||||
* Errors from `jwe.Decrypt` and `jwe.Encrypt`, as well as `jwe.Parse` (and friends)
|
||||
can now be differentiated by using `errors.Is`. All `jwe.IsXXXXrror` functions
|
||||
have been removed.
|
||||
|
||||
## JWK
|
||||
|
||||
* All convenience accessors (e.g. `Algorithm`, `Crv`) now return `(T, bool)` instead
|
||||
of just `T`, except `KeyType`, which _always_ returns a valid value. If you want a
|
||||
single return value accessor, use `Get(dst) error` instead.
|
||||
|
||||
* `jwk.KeyUsageType` can now be configured so that it's possible to assign values
|
||||
other than "sig" and "enc" via `jwk.RegisterKeyUsage()`. Furthermore, strict
|
||||
checks can be turned on/off against these registered values
|
||||
|
||||
* `jwk.Cache` has been completely re-worked based on github.com/lestrrat-go/httprc/v3.
|
||||
In particular, the default whitelist mode has changed from "block everything" to
|
||||
"allow everything".
|
||||
|
||||
* Experimental secp256k1 encoding/decoding for PEM encoded ASN.1 DER Format
|
||||
has been removed. Instead, `jwk.PEMDecoder` and `jwk.PEMEncoder` have been
|
||||
added to support those who want to perform non-standard PEM encoding/decoding
|
||||
|
||||
* Iterators have been completely removed.
|
||||
|
||||
* `jwk/x25519` has been removed. To use X25519 keys, use `(crypto/ecdh).PrivateKey` and
|
||||
`(crypto/ecdh).PublicKey`. Similarly, internals have been reworked to use `crypto/ecdh`
|
||||
|
||||
* Parsing has completely been reworked. It is now possible to add your own `jwk.KeyParser`
|
||||
to generate a custom `jwk.Key` that this library may not natively support. Also see
|
||||
`jwk.RegisterKeyParser()`
|
||||
|
||||
* `jwk.KeyProbe` has been added to aid probing the JSON message. This is used to
|
||||
guess the type of key described in the JSON message before deciding which concrete
|
||||
type to instantiate, and aids implementing your own `jwk.KeyParser`. Also see
|
||||
`jwk.RegisterKeyProbe()`
|
||||
|
||||
* Conversion between raw keys and `jwk.Key` can be customized using `jwk.KeyImporter` and `jwk.KeyExporter`.
|
||||
Also see `jwk.RegisterKeyImporter()` and `jwk.RegisterKeyExporter()`
|
||||
|
||||
* Added `jwk/ecdsa` to keep track of which curves are available for ECDSA keys.
|
||||
|
||||
* `(jwk.Key).Raw()` has been deprecated. Use `jwk.Export()` instead to convert `jwk.Key`
|
||||
objects into their "raw" versions (e.g. `*rsa.PrivateKey`, `*ecdsa.PrivateKey`, etc).
|
||||
This is to allow third parties to register custom key types that this library does not
|
||||
natively support: Whereas a method must be bound to an object, and thus does not necessarily
|
||||
have a way to hook into a global settings (i.e. custom exporter/importer) for arbitrary
|
||||
key types, if the entrypoint is a function it's much easier and cleaner to for third-parties
|
||||
to take advantage and hook into the mechanisms.
|
||||
|
||||
* `jwk.FromRaw()` has been derepcated. Use `jwk.Import()` instead to convert "raw"
|
||||
keys (e.g. `*rsa.PrivateKEy`, `*Ecdsa.PrivateKey`, etc) int `jwk.Key`s.
|
||||
|
||||
* `(jwk.Key).FromRaw()` has been deprecated. The method `(jwk.Key).Import()` still exist for
|
||||
built-in types, but it is no longer part of any public API (`interface{}`).
|
||||
|
||||
* `jwk.Fetch` is marked as a simple wrapper around `net/http` and `jwk.Parse`.
|
||||
|
||||
* `jwk.SetGlobalFetcher` has been deprecated.
|
||||
|
||||
* `jwk.Fetcher` has been clearly marked as something that has limited
|
||||
usage for `jws.WithVerifyAuto`
|
||||
|
||||
* `jwk.Key` with P256/P386/P521 curves can be exporrted to `ecdh.PrivateKey`/`ecdh.PublicKey`
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 lestrrat
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
module(
|
||||
name = "com_github_lestrrat_go_jwx_v3",
|
||||
version = "3.0.0",
|
||||
repo_name = "com_github_lestrrat_go_jwx_v2",
|
||||
)
|
||||
|
||||
bazel_dep(name = "bazel_skylib", version = "1.7.1")
|
||||
bazel_dep(name = "rules_go", version = "0.55.1")
|
||||
bazel_dep(name = "gazelle", version = "0.44.0")
|
||||
bazel_dep(name = "aspect_bazel_lib", version = "2.11.0")
|
||||
|
||||
# Go SDK setup from go.mod
|
||||
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
|
||||
go_sdk.from_file(go_mod = "//:go.mod")
|
||||
|
||||
# Go dependencies from go.mod
|
||||
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
|
||||
go_deps.from_file(go_mod = "//:go.mod")
|
||||
|
||||
# Use repositories for external Go dependencies
|
||||
use_repo(
|
||||
go_deps,
|
||||
"com_github_decred_dcrd_dcrec_secp256k1_v4",
|
||||
"com_github_goccy_go_json",
|
||||
"com_github_lestrrat_go_blackmagic",
|
||||
"com_github_lestrrat_go_dsig",
|
||||
"com_github_lestrrat_go_dsig_secp256k1",
|
||||
"com_github_lestrrat_go_httprc_v3",
|
||||
"com_github_lestrrat_go_option_v2",
|
||||
"com_github_segmentio_asm",
|
||||
"com_github_stretchr_testify",
|
||||
"com_github_valyala_fastjson",
|
||||
"org_golang_x_crypto",
|
||||
)
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
{
|
||||
"lockFileVersion": 18,
|
||||
"registryFileHashes": {
|
||||
"https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed",
|
||||
"https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/MODULE.bazel": "cb1ba9f9999ed0bc08600c221f532c1ddd8d217686b32ba7d45b0713b5131452",
|
||||
"https://bcr.bazel.build/modules/aspect_bazel_lib/2.11.0/source.json": "92494d5aa43b96665397dd13ee16023097470fa85e276b93674d62a244de47ee",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
|
||||
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b",
|
||||
"https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84",
|
||||
"https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.44.0/MODULE.bazel": "fd3177ca0938da57a1e416cad3f39b9c4334defbc717e89aba9d9ddbbb0341da",
|
||||
"https://bcr.bazel.build/modules/gazelle/0.44.0/source.json": "7fb65ef9c1ce470d099ca27fd478673d9d64c844af28d0d472b0874c7d590cb6",
|
||||
"https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb",
|
||||
"https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4",
|
||||
"https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075",
|
||||
"https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d",
|
||||
"https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92",
|
||||
"https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814",
|
||||
"https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d",
|
||||
"https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c",
|
||||
"https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e",
|
||||
"https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573",
|
||||
"https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e",
|
||||
"https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206",
|
||||
"https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8",
|
||||
"https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.51.0/MODULE.bazel": "b6920f505935bfd69381651c942496d99b16e2a12f3dd5263b90ded16f3b4d0f",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.55.1/MODULE.bazel": "a57a6fc59a74326c0b440d07cca209edf13c7d1a641e48cfbeab56e79f873609",
|
||||
"https://bcr.bazel.build/modules/rules_go/0.55.1/source.json": "827a740c8959c9d20616889e7746cde4dcc6ee80d25146943627ccea0736328f",
|
||||
"https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74",
|
||||
"https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6",
|
||||
"https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
|
||||
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
|
||||
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
|
||||
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
|
||||
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
|
||||
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
|
||||
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f",
|
||||
"https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73",
|
||||
"https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2",
|
||||
"https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7",
|
||||
"https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b",
|
||||
"https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7",
|
||||
"https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01",
|
||||
"https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
|
||||
},
|
||||
"selectedYankedVersions": {},
|
||||
"moduleExtensions": {
|
||||
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
|
||||
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
"envVariables": {},
|
||||
"generatedRepoSpecs": {
|
||||
"com_github_jetbrains_kotlin_git": {
|
||||
"repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip"
|
||||
],
|
||||
"sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88"
|
||||
}
|
||||
},
|
||||
"com_github_jetbrains_kotlin": {
|
||||
"repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository",
|
||||
"attributes": {
|
||||
"git_repository_name": "com_github_jetbrains_kotlin_git",
|
||||
"compiler_version": "1.9.23"
|
||||
}
|
||||
},
|
||||
"com_github_google_ksp": {
|
||||
"repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository",
|
||||
"attributes": {
|
||||
"urls": [
|
||||
"https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip"
|
||||
],
|
||||
"sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d",
|
||||
"strip_version": "1.9.23-1.0.20"
|
||||
}
|
||||
},
|
||||
"com_github_pinterest_ktlint": {
|
||||
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file",
|
||||
"attributes": {
|
||||
"sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985",
|
||||
"urls": [
|
||||
"https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint"
|
||||
],
|
||||
"executable": true
|
||||
}
|
||||
},
|
||||
"rules_android": {
|
||||
"repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
|
||||
"attributes": {
|
||||
"sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806",
|
||||
"strip_prefix": "rules_android-0.1.1",
|
||||
"urls": [
|
||||
"https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"recordedRepoMappingEntries": [
|
||||
[
|
||||
"rules_kotlin+",
|
||||
"bazel_tools",
|
||||
"bazel_tools"
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
.PHONY: generate realclean cover viewcover test lint check_diffs imports tidy jwx
|
||||
generate:
|
||||
@go generate
|
||||
@$(MAKE) generate-jwa generate-jwe generate-jwk generate-jws generate-jwt
|
||||
@./tools/cmd/gofmt.sh
|
||||
|
||||
generate-%:
|
||||
@go generate $(shell pwd -P)/$(patsubst generate-%,%,$@)
|
||||
|
||||
realclean:
|
||||
rm coverage.out
|
||||
|
||||
test-cmd:
|
||||
env TESTOPTS="$(TESTOPTS)" ./tools/test.sh
|
||||
|
||||
test:
|
||||
$(MAKE) test-stdlib TESTOPTS=
|
||||
|
||||
test-stdlib:
|
||||
$(MAKE) test-cmd TESTOPTS=
|
||||
|
||||
test-goccy:
|
||||
$(MAKE) test-cmd TESTOPTS="-tags jwx_goccy"
|
||||
|
||||
test-es256k:
|
||||
$(MAKE) test-cmd TESTOPTS="-tags jwx_es256k"
|
||||
|
||||
test-secp256k1-pem:
|
||||
$(MAKE) test-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1_pem"
|
||||
|
||||
test-asmbase64:
|
||||
$(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64"
|
||||
|
||||
test-alltags:
|
||||
$(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k,jwx_secp256k1_pem"
|
||||
|
||||
cover-cmd:
|
||||
env MODE=cover ./tools/test.sh
|
||||
|
||||
cover:
|
||||
$(MAKE) cover-stdlib
|
||||
|
||||
cover-stdlib:
|
||||
$(MAKE) cover-cmd TESTOPTS=
|
||||
|
||||
cover-goccy:
|
||||
$(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy"
|
||||
|
||||
cover-es256k:
|
||||
$(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k"
|
||||
|
||||
cover-secp256k1-pem:
|
||||
$(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1"
|
||||
|
||||
cover-asmbase64:
|
||||
$(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64"
|
||||
|
||||
cover-alltags:
|
||||
$(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k,jwx_secp256k1_pem"
|
||||
|
||||
smoke-cmd:
|
||||
env MODE=short ./tools/test.sh
|
||||
|
||||
smoke:
|
||||
$(MAKE) smoke-stdlib
|
||||
|
||||
smoke-stdlib:
|
||||
$(MAKE) smoke-cmd TESTOPTS=
|
||||
|
||||
smoke-goccy:
|
||||
$(MAKE) smoke-cmd TESTOPTS="-tags jwx_goccy"
|
||||
|
||||
smoke-es256k:
|
||||
$(MAKE) smoke-cmd TESTOPTS="-tags jwx_es256k"
|
||||
|
||||
smoke-secp256k1-pem:
|
||||
$(MAKE) smoke-cmd TESTOPTS="-tags jwx_es256k,jwx_secp256k1_pem"
|
||||
|
||||
smoke-alltags:
|
||||
$(MAKE) smoke-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k,jwx_secp256k1_pem"
|
||||
|
||||
viewcover:
|
||||
go tool cover -html=coverage.out
|
||||
|
||||
lint:
|
||||
golangci-lint run ./...
|
||||
|
||||
check_diffs:
|
||||
./scripts/check-diff.sh
|
||||
|
||||
imports:
|
||||
goimports -w ./
|
||||
|
||||
tidy:
|
||||
./scripts/tidy.sh
|
||||
|
||||
jwx:
|
||||
@./tools/cmd/install-jwx.sh
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
# github.com/lestrrat-go/jwx/v3 [](https://github.com/lestrrat-go/jwx/actions/workflows/ci.yml) [](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3) [](https://codecov.io/github/lestrrat-go/jwx?branch=v3)
|
||||
|
||||
Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies.
|
||||
|
||||
If you are using this module in your product or your company, please add your product and/or company name in the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users)! It really helps keeping up our motivation.
|
||||
|
||||
# Features
|
||||
|
||||
* Complete coverage of JWA/JWE/JWK/JWS/JWT, not just JWT+minimum tool set.
|
||||
* Supports JWS messages with multiple signatures, both compact and JSON serialization
|
||||
* Supports JWS with detached payload
|
||||
* Supports JWS with unencoded payload (RFC7797)
|
||||
* Supports JWE messages with multiple recipients, both compact and JSON serialization
|
||||
* Most operations work with either JWK or raw keys e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc).
|
||||
* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convention
|
||||
* jws.Parse/Verify/Sign
|
||||
* jwe.Parse/Encrypt/Decrypt
|
||||
* Arguments are organized as explicit required parameters and optional WithXXXX() style options.
|
||||
* Extra utilities
|
||||
* `jwk.Cache` to always keep a JWKS up-to-date
|
||||
* [bazel](https://bazel.build)-ready
|
||||
|
||||
Some more in-depth discussion on why you might want to use this library over others
|
||||
can be found in the [Description section](#description)
|
||||
|
||||
If you are using v0 or v1, you are strongly encouraged to migrate to using v3
|
||||
(the version that comes with the README you are reading).
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
<!-- INCLUDE(examples/jwx_readme_example_test.go) -->
|
||||
```go
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/jwx/v3/jws"
|
||||
"github.com/lestrrat-go/jwx/v3/jwt"
|
||||
)
|
||||
|
||||
func Example() {
|
||||
// Parse, serialize, slice and dice JWKs!
|
||||
privkey, err := jwk.ParseKey(jsonRSAPrivateKey)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to parse JWK: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
pubkey, err := jwk.PublicKeyOf(privkey)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to get public key: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Work with JWTs!
|
||||
{
|
||||
// Build a JWT!
|
||||
tok, err := jwt.NewBuilder().
|
||||
Issuer(`github.com/lestrrat-go/jwx`).
|
||||
IssuedAt(time.Now()).
|
||||
Build()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to build token: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign a JWT!
|
||||
signed, err := jwt.Sign(tok, jwt.WithKey(jwa.RS256(), privkey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign token: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify a JWT!
|
||||
{
|
||||
verifiedToken, err := jwt.Parse(signed, jwt.WithKey(jwa.RS256(), pubkey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify JWS: %s\n", err)
|
||||
return
|
||||
}
|
||||
_ = verifiedToken
|
||||
}
|
||||
|
||||
// Work with *http.Request!
|
||||
{
|
||||
req, err := http.NewRequest(http.MethodGet, `https://github.com/lestrrat-go/jwx`, nil)
|
||||
req.Header.Set(`Authorization`, fmt.Sprintf(`Bearer %s`, signed))
|
||||
|
||||
verifiedToken, err := jwt.ParseRequest(req, jwt.WithKey(jwa.RS256(), pubkey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify token from HTTP request: %s\n", err)
|
||||
return
|
||||
}
|
||||
_ = verifiedToken
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypt and Decrypt arbitrary payload with JWE!
|
||||
{
|
||||
encrypted, err := jwe.Encrypt(payloadLoremIpsum, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPublicKey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to encrypt payload: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP(), jwkRSAPrivateKey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to decrypt payload: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(decrypted, payloadLoremIpsum) {
|
||||
fmt.Printf("verified payload did not match\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Sign and Verify arbitrary payload with JWS!
|
||||
{
|
||||
signed, err := jws.Sign(payloadLoremIpsum, jws.WithKey(jwa.RS256(), jwkRSAPrivateKey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to sign payload: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
verified, err := jws.Verify(signed, jws.WithKey(jwa.RS256(), jwkRSAPublicKey))
|
||||
if err != nil {
|
||||
fmt.Printf("failed to verify payload: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !bytes.Equal(verified, payloadLoremIpsum) {
|
||||
fmt.Printf("verified payload did not match\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
source: [examples/jwx_readme_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwx_readme_example_test.go)
|
||||
<!-- END INCLUDE -->
|
||||
|
||||
# How-to Documentation
|
||||
|
||||
* [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3)
|
||||
* [How-to style documentation](./docs)
|
||||
* [Runnable Examples](./examples)
|
||||
|
||||
# Description
|
||||
|
||||
This Go module implements JWA, JWE, JWK, JWS, and JWT. Please see the following table for the list of
|
||||
available packages:
|
||||
|
||||
| Package name | Notes |
|
||||
|-----------------------------------------------------------|-------------------------------------------------|
|
||||
| [jwt](https://github.com/lestrrat-go/jwx/tree/v3/jwt) | [RFC 7519](https://tools.ietf.org/html/rfc7519) |
|
||||
| [jwk](https://github.com/lestrrat-go/jwx/tree/v3/jwk) | [RFC 7517](https://tools.ietf.org/html/rfc7517) + [RFC 7638](https://tools.ietf.org/html/rfc7638) |
|
||||
| [jwa](https://github.com/lestrrat-go/jwx/tree/v3/jwa) | [RFC 7518](https://tools.ietf.org/html/rfc7518) |
|
||||
| [jws](https://github.com/lestrrat-go/jwx/tree/v3/jws) | [RFC 7515](https://tools.ietf.org/html/rfc7515) + [RFC 7797](https://tools.ietf.org/html/rfc7797) |
|
||||
| [jwe](https://github.com/lestrrat-go/jwx/tree/v3/jwe) | [RFC 7516](https://tools.ietf.org/html/rfc7516) |
|
||||
## History
|
||||
|
||||
My goal was to write a server that heavily uses JWK and JWT. At first glance
|
||||
the libraries that already exist seemed sufficient, but soon I realized that
|
||||
|
||||
1. To completely implement the protocols, I needed the entire JWT, JWK, JWS, JWE (and JWA, by necessity).
|
||||
2. Most of the libraries that existed only deal with a subset of the various JWx specifications that were necessary to implement their specific needs
|
||||
|
||||
For example, a certain library looks like it had most of JWS, JWE, JWK covered, but then it lacked the ability to include private claims in its JWT responses. Another library had support of all the private claims, but completely lacked in its flexibility to generate various different response formats.
|
||||
|
||||
Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing.
|
||||
|
||||
So here's `github.com/lestrrat-go/jwx/v3`. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.
|
||||
|
||||
## Why would I use this library?
|
||||
|
||||
There are several other major Go modules that handle JWT and related data formats,
|
||||
so why should you use this library?
|
||||
|
||||
From a purely functional perspective, the only major difference is this:
|
||||
Whereas most other projects only deal with what they seem necessary to handle
|
||||
JWTs, this module handles the **_entire_** spectrum of JWS, JWE, JWK, and JWT.
|
||||
|
||||
That is, if you need to not only parse JWTs, but also to control JWKs, or
|
||||
if you need to handle payloads that are NOT JWTs, you should probably consider
|
||||
using this module. You should also note that JWT is built _on top_ of those
|
||||
other technologies. You simply cannot have a complete JWT package without
|
||||
implementing the entirety of JWS/JWE/JWK, which this library does.
|
||||
|
||||
Next, from an implementation perspective, this module differs significantly
|
||||
from others in that it tries very hard to expose only the APIs, and not the
|
||||
internal data. For example, individual JWT claims are not accessible through
|
||||
struct field lookups. You need to use one of the getter methods.
|
||||
|
||||
This is because this library takes the stance that the end user is fully capable
|
||||
and even willing to shoot themselves on the foot when presented with a lax
|
||||
API. By making sure that users do not have access to open structs, we can protect
|
||||
users from doing silly things like creating _incomplete_ structs, or access the
|
||||
structs concurrently without any protection. This structure also allows
|
||||
us to put extra smarts in the structs, such as doing the right thing when
|
||||
you want to parse / write custom fields (this module does not require the user
|
||||
to specify alternate structs to parse objects with custom fields)
|
||||
|
||||
In the end I think it comes down to your usage pattern, and priorities.
|
||||
Some general guidelines that come to mind are:
|
||||
|
||||
* If you want a single library to handle everything JWx, such as using JWE, JWK, JWS, handling [auto-refreshing JWKs](https://github.com/lestrrat-go/jwx/blob/v3/docs/04-jwk.md#auto-refreshing-remote-keys), use this module.
|
||||
* If you want to honor all possible custom fields transparently, use this module.
|
||||
* If you want a standardized clean API, use this module.
|
||||
|
||||
Otherwise, feel free to choose something else.
|
||||
|
||||
# Contributions
|
||||
|
||||
## Issues
|
||||
|
||||
For bug reports and feature requests, please try to follow the issue templates as much as possible.
|
||||
For either bug reports or feature requests, failing tests are even better.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Please make sure to include tests that exercise the changes you made.
|
||||
|
||||
If you are editing auto-generated files (those files with the `_gen.go` suffix, please make sure that you do the following:
|
||||
|
||||
1. Edit the generator, not the generated files (e.g. internal/cmd/genreadfile/main.go)
|
||||
2. Run `make generate` (or `go generate`) to generate the new code
|
||||
3. Commit _both_ the generator _and_ the generated files
|
||||
|
||||
## Discussions / Usage
|
||||
|
||||
Please try [discussions](https://github.com/lestrrat-go/jwx/tree/v3/discussions) first.
|
||||
|
||||
# Related Modules
|
||||
|
||||
* [github.com/lestrrat-go/echo-middleware-jwx](https://github.com/lestrrat-go/echo-middleware-jwx) - Sample Echo middleware
|
||||
* [github.com/jwx-go/crypto-signer/gcp](https://github.com/jwx-go/crypto-signer/tree/main/gcp) - GCP KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
|
||||
* [github.com/jwx-go/crypto-signer/aws](https://github.com/jwx-go/crypto-signer/tree/main/aws) - AWS KMS wrapper that implements [`crypto.Signer`](https://pkg.go.dev/crypto#Signer)
|
||||
|
||||
# Credits
|
||||
|
||||
* Initial work on this library was generously sponsored by HDE Inc (https://www.hde.co.jp)
|
||||
* Lots of code, especially JWE was initially taken from go-jose library (https://github.com/square/go-jose)
|
||||
* Lots of individual contributors have helped this project over the years. Thank each and everyone of you very much.
|
||||
|
||||
# Quid pro quo
|
||||
|
||||
If you use this software to build products in a for-profit organization, we ask you to _consider_
|
||||
contributing back to FOSS in the following manner:
|
||||
|
||||
* For every 100 employees (direct hires) of your organization, please consider contributing minimum of $1 every year to either this project, **or** another FOSS projects that this project uses. For example, for 100 employees, we ask you contribute $100 yearly; for 10,000 employees, we ask you contribute $10,000 yearly.
|
||||
* If possible, please make this information public. You do not need to disclose the amount you are contributing, but please make the information that you are contributing to particular FOSS projects public. For this project, please consider writing your name on the [Wiki](https://github.com/lestrrat-go/jwx/wiki/Users)
|
||||
|
||||
This is _NOT_ a licensing term: you are still free to use this software according to the license it
|
||||
comes with. This clause is only a plea for people to acknowledge the work from FOSS developers whose
|
||||
work you rely on each and everyday.
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Most recent two major versions will receive security updates
|
||||
|
||||
| Version | Supported |
|
||||
| -------- | ------------------ |
|
||||
| v3.x.x | :white_check_mark: |
|
||||
| v2.x.x | :white_check_mark: |
|
||||
| < v2.0.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you think you found a vulnerability, please report it via [GitHub Security Advisory](https://github.com/lestrrat-go/jwx/security/advisories/new).
|
||||
Please include explicit steps to reproduce the security issue.
|
||||
|
||||
We will do our best to respond in a timely manner, but please also be aware that this project is maintained by a very limited number of people. Please help us with test code and such.
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# Empty WORKSPACE file for bzlmod compatibility
|
||||
# All dependencies are now managed in MODULE.bazel
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "cert",
|
||||
srcs = [
|
||||
"cert.go",
|
||||
"chain.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/cert",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/base64",
|
||||
"//internal/tokens",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "cert_test",
|
||||
srcs = [
|
||||
"cert_test.go",
|
||||
"chain_test.go",
|
||||
],
|
||||
deps = [
|
||||
":cert",
|
||||
"//internal/jwxtest",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":cert",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
stdlibb64 "encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
)
|
||||
|
||||
// Create is a wrapper around x509.CreateCertificate, but it additionally
|
||||
// encodes it in base64 so that it can be easily added to `x5c` fields
|
||||
func Create(rand io.Reader, template, parent *x509.Certificate, pub, priv any) ([]byte, error) {
|
||||
der, err := x509.CreateCertificate(rand, template, parent, pub, priv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create x509 certificate: %w`, err)
|
||||
}
|
||||
return EncodeBase64(der)
|
||||
}
|
||||
|
||||
// EncodeBase64 is a utility function to encode ASN.1 DER certificates
|
||||
// using base64 encoding. This operation is normally done by `pem.Encode`
|
||||
// but since PEM would include the markers (`-----BEGIN`, and the like)
|
||||
// while `x5c` fields do not need this, this function can be used to
|
||||
// shave off a few lines
|
||||
func EncodeBase64(der []byte) ([]byte, error) {
|
||||
enc := stdlibb64.StdEncoding
|
||||
dst := make([]byte, enc.EncodedLen(len(der)))
|
||||
enc.Encode(dst, der)
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
// Parse is a utility function to decode a base64 encoded
|
||||
// ASN.1 DER format certificate, and to parse the byte sequence.
|
||||
// The certificate must be in PKIX format, and it must not contain PEM markers
|
||||
func Parse(src []byte) (*x509.Certificate, error) {
|
||||
dst, err := base64.Decode(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to base64 decode the certificate: %w`, err)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(dst)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to parse x509 certificate: %w`, err)
|
||||
}
|
||||
return cert, nil
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
// Chain represents a certificate chain as used in the `x5c` field of
|
||||
// various objects within JOSE.
|
||||
//
|
||||
// It stores the certificates as a list of base64 encoded []byte
|
||||
// sequence. By definition these values must PKIX encoded.
|
||||
type Chain struct {
|
||||
certificates [][]byte
|
||||
}
|
||||
|
||||
func (cc Chain) MarshalJSON() ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteByte(tokens.OpenSquareBracket)
|
||||
for i, cert := range cc.certificates {
|
||||
if i > 0 {
|
||||
buf.WriteByte(tokens.Comma)
|
||||
}
|
||||
buf.WriteByte('"')
|
||||
buf.Write(cert)
|
||||
buf.WriteByte('"')
|
||||
}
|
||||
buf.WriteByte(tokens.CloseSquareBracket)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (cc *Chain) UnmarshalJSON(data []byte) error {
|
||||
var tmp []string
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal certificate chain: %w`, err)
|
||||
}
|
||||
|
||||
certs := make([][]byte, len(tmp))
|
||||
for i, cert := range tmp {
|
||||
certs[i] = []byte(cert)
|
||||
}
|
||||
cc.certificates = certs
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns the n-th ASN.1 DER + base64 encoded certificate
|
||||
// stored. `false` will be returned in the second argument if
|
||||
// the corresponding index is out of range.
|
||||
func (cc *Chain) Get(index int) ([]byte, bool) {
|
||||
if index < 0 || index >= len(cc.certificates) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return cc.certificates[index], true
|
||||
}
|
||||
|
||||
// Len returns the number of certificates stored in this Chain
|
||||
func (cc *Chain) Len() int {
|
||||
return len(cc.certificates)
|
||||
}
|
||||
|
||||
var pemStart = []byte("----- BEGIN CERTIFICATE -----")
|
||||
var pemEnd = []byte("----- END CERTIFICATE -----")
|
||||
|
||||
func (cc *Chain) AddString(der string) error {
|
||||
return cc.Add([]byte(der))
|
||||
}
|
||||
|
||||
func (cc *Chain) Add(der []byte) error {
|
||||
// We're going to be nice and remove marker lines if they
|
||||
// give it to us
|
||||
der = bytes.TrimPrefix(der, pemStart)
|
||||
der = bytes.TrimSuffix(der, pemEnd)
|
||||
der = bytes.TrimSpace(der)
|
||||
cc.certificates = append(cc.certificates, der)
|
||||
return nil
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
codecov:
|
||||
allow_coverage_offsets: true
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package jwx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
type FormatKind int
|
||||
|
||||
// These constants describe the result from guessing the format
|
||||
// of the incoming buffer.
|
||||
const (
|
||||
// InvalidFormat is returned when the format of the incoming buffer
|
||||
// has been deemed conclusively invalid
|
||||
InvalidFormat FormatKind = iota
|
||||
// UnknownFormat is returned when GuessFormat was not able to conclusively
|
||||
// determine the format of the
|
||||
UnknownFormat
|
||||
JWE
|
||||
JWS
|
||||
JWK
|
||||
JWKS
|
||||
JWT
|
||||
)
|
||||
|
||||
type formatHint struct {
|
||||
Payload json.RawMessage `json:"payload"` // Only in JWS
|
||||
Signatures json.RawMessage `json:"signatures"` // Only in JWS
|
||||
Ciphertext json.RawMessage `json:"ciphertext"` // Only in JWE
|
||||
KeyType json.RawMessage `json:"kty"` // Only in JWK
|
||||
Keys json.RawMessage `json:"keys"` // Only in JWKS
|
||||
Audience json.RawMessage `json:"aud"` // Only in JWT
|
||||
}
|
||||
|
||||
// GuessFormat is used to guess the format the given payload is in
|
||||
// using heuristics. See the type FormatKind for a full list of
|
||||
// possible types.
|
||||
//
|
||||
// This may be useful in determining your next action when you may
|
||||
// encounter a payload that could either be a JWE, JWS, or a plain JWT.
|
||||
//
|
||||
// Because JWTs are almost always JWS signed, you may be thrown off
|
||||
// if you pass what you think is a JWT payload to this function.
|
||||
// If the function is in the "Compact" format, it means it's a JWS
|
||||
// signed message, and its payload is the JWT. Therefore this function
|
||||
// will return JWS, not JWT.
|
||||
//
|
||||
// This function requires an extra parsing of the payload, and therefore
|
||||
// may be inefficient if you call it every time before parsing.
|
||||
func GuessFormat(payload []byte) FormatKind {
|
||||
// The check against kty, keys, and aud are something this library
|
||||
// made up. for the distinctions between JWE and JWS, we used
|
||||
// https://datatracker.ietf.org/doc/html/rfc7516#section-9.
|
||||
//
|
||||
// The above RFC described several ways to distinguish between
|
||||
// a JWE and JWS JSON, but we're only using one of them
|
||||
|
||||
payload = bytes.TrimSpace(payload)
|
||||
if len(payload) <= 0 {
|
||||
return UnknownFormat
|
||||
}
|
||||
|
||||
if payload[0] != tokens.OpenCurlyBracket {
|
||||
// Compact format. It's probably a JWS or JWE
|
||||
sep := []byte{tokens.Period} // I want to const this :/
|
||||
|
||||
// Note: this counts the number of occurrences of the
|
||||
// separator, but the RFC talks about the number of segments.
|
||||
// number of tokens.Period == segments - 1, so that's why we have 2 and 4 here
|
||||
switch count := bytes.Count(payload, sep); count {
|
||||
case 2:
|
||||
return JWS
|
||||
case 4:
|
||||
return JWE
|
||||
default:
|
||||
return InvalidFormat
|
||||
}
|
||||
}
|
||||
|
||||
// If we got here, we probably have JSON.
|
||||
var h formatHint
|
||||
if err := json.Unmarshal(payload, &h); err != nil {
|
||||
return UnknownFormat
|
||||
}
|
||||
|
||||
if h.Audience != nil {
|
||||
return JWT
|
||||
}
|
||||
if h.KeyType != nil {
|
||||
return JWK
|
||||
}
|
||||
if h.Keys != nil {
|
||||
return JWKS
|
||||
}
|
||||
if h.Ciphertext != nil {
|
||||
return JWE
|
||||
}
|
||||
if h.Signatures != nil && h.Payload != nil {
|
||||
return JWS
|
||||
}
|
||||
return UnknownFormat
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated by "stringer -type=FormatKind"; DO NOT EDIT.
|
||||
|
||||
package jwx
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[InvalidFormat-0]
|
||||
_ = x[UnknownFormat-1]
|
||||
_ = x[JWE-2]
|
||||
_ = x[JWS-3]
|
||||
_ = x[JWK-4]
|
||||
_ = x[JWKS-5]
|
||||
_ = x[JWT-6]
|
||||
}
|
||||
|
||||
const _FormatKind_name = "InvalidFormatUnknownFormatJWEJWSJWKJWKSJWT"
|
||||
|
||||
var _FormatKind_index = [...]uint8{0, 13, 26, 29, 32, 35, 39, 42}
|
||||
|
||||
func (i FormatKind) String() string {
|
||||
idx := int(i) - 0
|
||||
if i < 0 || idx >= len(_FormatKind_index)-1 {
|
||||
return "FormatKind(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _FormatKind_name[_FormatKind_index[idx]:_FormatKind_index[idx+1]]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "base64",
|
||||
srcs = ["base64.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/base64",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "base64_test",
|
||||
srcs = ["base64_test.go"],
|
||||
embed = [":base64"],
|
||||
deps = ["@com_github_stretchr_testify//require"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":base64",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
//go:build jwx_asmbase64
|
||||
|
||||
package base64
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
asmbase64 "github.com/segmentio/asm/base64"
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetEncoder(asmEncoder{asmbase64.RawURLEncoding})
|
||||
SetDecoder(asmDecoder{})
|
||||
}
|
||||
|
||||
type asmEncoder struct {
|
||||
*asmbase64.Encoding
|
||||
}
|
||||
|
||||
func (e asmEncoder) AppendEncode(dst, src []byte) []byte {
|
||||
n := e.Encoding.EncodedLen(len(src))
|
||||
dst = slices.Grow(dst, n)
|
||||
e.Encoding.Encode(dst[len(dst):][:n], src)
|
||||
return dst[:len(dst)+n]
|
||||
}
|
||||
|
||||
type asmDecoder struct{}
|
||||
|
||||
func (d asmDecoder) Decode(src []byte) ([]byte, error) {
|
||||
var enc *asmbase64.Encoding
|
||||
switch Guess(src) {
|
||||
case Std:
|
||||
enc = asmbase64.StdEncoding
|
||||
case RawStd:
|
||||
enc = asmbase64.RawStdEncoding
|
||||
case URL:
|
||||
enc = asmbase64.URLEncoding
|
||||
case RawURL:
|
||||
enc = asmbase64.RawURLEncoding
|
||||
default:
|
||||
return nil, fmt.Errorf(`invalid encoding`)
|
||||
}
|
||||
|
||||
dst := make([]byte, enc.DecodedLen(len(src)))
|
||||
n, err := enc.Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode source: %w`, err)
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package base64
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Decoder interface {
|
||||
Decode([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type Encoder interface {
|
||||
Encode([]byte, []byte)
|
||||
EncodedLen(int) int
|
||||
EncodeToString([]byte) string
|
||||
AppendEncode([]byte, []byte) []byte
|
||||
}
|
||||
|
||||
var muEncoder sync.RWMutex
|
||||
var encoder Encoder = base64.RawURLEncoding
|
||||
var muDecoder sync.RWMutex
|
||||
var decoder Decoder = defaultDecoder{}
|
||||
|
||||
func SetEncoder(enc Encoder) {
|
||||
muEncoder.Lock()
|
||||
defer muEncoder.Unlock()
|
||||
encoder = enc
|
||||
}
|
||||
|
||||
func getEncoder() Encoder {
|
||||
muEncoder.RLock()
|
||||
defer muEncoder.RUnlock()
|
||||
return encoder
|
||||
}
|
||||
|
||||
func DefaultEncoder() Encoder {
|
||||
return getEncoder()
|
||||
}
|
||||
|
||||
func SetDecoder(dec Decoder) {
|
||||
muDecoder.Lock()
|
||||
defer muDecoder.Unlock()
|
||||
decoder = dec
|
||||
}
|
||||
|
||||
func getDecoder() Decoder {
|
||||
muDecoder.RLock()
|
||||
defer muDecoder.RUnlock()
|
||||
return decoder
|
||||
}
|
||||
|
||||
func Encode(src []byte) []byte {
|
||||
encoder := getEncoder()
|
||||
dst := make([]byte, encoder.EncodedLen(len(src)))
|
||||
encoder.Encode(dst, src)
|
||||
return dst
|
||||
}
|
||||
|
||||
func EncodeToString(src []byte) string {
|
||||
return getEncoder().EncodeToString(src)
|
||||
}
|
||||
|
||||
func EncodeUint64ToString(v uint64) string {
|
||||
data := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(data, v)
|
||||
|
||||
i := 0
|
||||
for ; i < len(data); i++ {
|
||||
if data[i] != 0x0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return EncodeToString(data[i:])
|
||||
}
|
||||
|
||||
const (
|
||||
InvalidEncoding = iota
|
||||
Std
|
||||
URL
|
||||
RawStd
|
||||
RawURL
|
||||
)
|
||||
|
||||
func Guess(src []byte) int {
|
||||
var isRaw = !bytes.HasSuffix(src, []byte{'='})
|
||||
var isURL = !bytes.ContainsAny(src, "+/")
|
||||
switch {
|
||||
case isRaw && isURL:
|
||||
return RawURL
|
||||
case isURL:
|
||||
return URL
|
||||
case isRaw:
|
||||
return RawStd
|
||||
default:
|
||||
return Std
|
||||
}
|
||||
}
|
||||
|
||||
// defaultDecoder is a Decoder that detects the encoding of the source and
|
||||
// decodes it accordingly. This shouldn't really be required per the spec, but
|
||||
// it exist because we have seen in the wild JWTs that are encoded using
|
||||
// various versions of the base64 encoding.
|
||||
type defaultDecoder struct{}
|
||||
|
||||
func (defaultDecoder) Decode(src []byte) ([]byte, error) {
|
||||
var enc *base64.Encoding
|
||||
|
||||
switch Guess(src) {
|
||||
case RawURL:
|
||||
enc = base64.RawURLEncoding
|
||||
case URL:
|
||||
enc = base64.URLEncoding
|
||||
case RawStd:
|
||||
enc = base64.RawStdEncoding
|
||||
case Std:
|
||||
enc = base64.StdEncoding
|
||||
default:
|
||||
return nil, fmt.Errorf(`invalid encoding`)
|
||||
}
|
||||
|
||||
dst := make([]byte, enc.DecodedLen(len(src)))
|
||||
n, err := enc.Decode(dst, src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode source: %w`, err)
|
||||
}
|
||||
return dst[:n], nil
|
||||
}
|
||||
|
||||
func Decode(src []byte) ([]byte, error) {
|
||||
return getDecoder().Decode(src)
|
||||
}
|
||||
|
||||
func DecodeString(src string) ([]byte, error) {
|
||||
return getDecoder().Decode([]byte(src))
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "ecutil",
|
||||
srcs = ["ecutil.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/ecutil",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":ecutil",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
// Package ecutil defines tools that help with elliptic curve related
|
||||
// computation
|
||||
package ecutil
|
||||
|
||||
import (
|
||||
"crypto/elliptic"
|
||||
"math/big"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// size of buffer that needs to be allocated for EC521 curve
|
||||
ec521BufferSize = 66 // (521 / 8) + 1
|
||||
)
|
||||
|
||||
var ecpointBufferPool = sync.Pool{
|
||||
New: func() any {
|
||||
// In most cases the curve bit size will be less than this length
|
||||
// so allocate the maximum, and keep reusing
|
||||
buf := make([]byte, 0, ec521BufferSize)
|
||||
return &buf
|
||||
},
|
||||
}
|
||||
|
||||
func getCrvFixedBuffer(size int) []byte {
|
||||
//nolint:forcetypeassert
|
||||
buf := *(ecpointBufferPool.Get().(*[]byte))
|
||||
if size > ec521BufferSize && cap(buf) < size {
|
||||
buf = append(buf, make([]byte, size-cap(buf))...)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
// ReleaseECPointBuffer releases the []byte buffer allocated.
|
||||
func ReleaseECPointBuffer(buf []byte) {
|
||||
buf = buf[:cap(buf)]
|
||||
buf[0] = 0x0
|
||||
for i := 1; i < len(buf); i *= 2 {
|
||||
copy(buf[i:], buf[:i])
|
||||
}
|
||||
buf = buf[:0]
|
||||
ecpointBufferPool.Put(&buf)
|
||||
}
|
||||
|
||||
func CalculateKeySize(crv elliptic.Curve) int {
|
||||
// We need to create a buffer that fits the entire curve.
|
||||
// If the curve size is 66, that fits in 9 bytes. If the curve
|
||||
// size is 64, it fits in 8 bytes.
|
||||
bits := crv.Params().BitSize
|
||||
|
||||
// For most common cases we know before hand what the byte length
|
||||
// is going to be. optimize
|
||||
var inBytes int
|
||||
switch bits {
|
||||
case 224, 256, 384: // TODO: use constant?
|
||||
inBytes = bits / 8
|
||||
case 521:
|
||||
inBytes = ec521BufferSize
|
||||
default:
|
||||
inBytes = bits / 8
|
||||
if (bits % 8) != 0 {
|
||||
inBytes++
|
||||
}
|
||||
}
|
||||
|
||||
return inBytes
|
||||
}
|
||||
|
||||
// AllocECPointBuffer allocates a buffer for the given point in the given
|
||||
// curve. This buffer should be released using the ReleaseECPointBuffer
|
||||
// function.
|
||||
func AllocECPointBuffer(v *big.Int, crv elliptic.Curve) []byte {
|
||||
buf := getCrvFixedBuffer(CalculateKeySize(crv))
|
||||
v.FillBytes(buf)
|
||||
return buf
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "json",
|
||||
srcs = [
|
||||
"json.go",
|
||||
"registry.go",
|
||||
"stdlib.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/json",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = ["//internal/base64"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":json",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
//go:build jwx_goccy
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type Decoder = json.Decoder
|
||||
type Delim = json.Delim
|
||||
type Encoder = json.Encoder
|
||||
type Marshaler = json.Marshaler
|
||||
type Number = json.Number
|
||||
type RawMessage = json.RawMessage
|
||||
type Unmarshaler = json.Unmarshaler
|
||||
|
||||
func Engine() string {
|
||||
return "github.com/goccy/go-json"
|
||||
}
|
||||
|
||||
// NewDecoder respects the values specified in DecoderSettings,
|
||||
// and creates a Decoder that has certain features turned on/off
|
||||
func NewDecoder(r io.Reader) *json.Decoder {
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
if UseNumber() {
|
||||
dec.UseNumber()
|
||||
}
|
||||
|
||||
return dec
|
||||
}
|
||||
|
||||
// NewEncoder is just a proxy for "encoding/json".NewEncoder
|
||||
func NewEncoder(w io.Writer) *json.Encoder {
|
||||
return json.NewEncoder(w)
|
||||
}
|
||||
|
||||
// Marshal is just a proxy for "encoding/json".Marshal
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// MarshalIndent is just a proxy for "encoding/json".MarshalIndent
|
||||
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||
return json.MarshalIndent(v, prefix, indent)
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
)
|
||||
|
||||
var useNumber uint32 // TODO: at some point, change to atomic.Bool
|
||||
|
||||
func UseNumber() bool {
|
||||
return atomic.LoadUint32(&useNumber) == 1
|
||||
}
|
||||
|
||||
// Sets the global configuration for json decoding
|
||||
func DecoderSettings(inUseNumber bool) {
|
||||
var val uint32
|
||||
if inUseNumber {
|
||||
val = 1
|
||||
}
|
||||
atomic.StoreUint32(&useNumber, val)
|
||||
}
|
||||
|
||||
// Unmarshal respects the values specified in DecoderSettings,
|
||||
// and uses a Decoder that has certain features turned on/off
|
||||
func Unmarshal(b []byte, v any) error {
|
||||
dec := NewDecoder(bytes.NewReader(b))
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func AssignNextBytesToken(dst *[]byte, dec *Decoder) error {
|
||||
var val string
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
return fmt.Errorf(`error reading next value: %w`, err)
|
||||
}
|
||||
|
||||
buf, err := base64.DecodeString(val)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`expected base64 encoded []byte (%T)`, val)
|
||||
}
|
||||
*dst = buf
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadNextStringToken(dec *Decoder) (string, error) {
|
||||
var val string
|
||||
if err := dec.Decode(&val); err != nil {
|
||||
return "", fmt.Errorf(`error reading next value: %w`, err)
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func AssignNextStringToken(dst **string, dec *Decoder) error {
|
||||
val, err := ReadNextStringToken(dec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dst = &val
|
||||
return nil
|
||||
}
|
||||
|
||||
// FlattenAudience is a flag to specify if we should flatten the "aud"
|
||||
// entry to a string when there's only one entry.
|
||||
// In jwx < 1.1.8 we just dumped everything as an array of strings,
|
||||
// but apparently AWS Cognito doesn't handle this well.
|
||||
//
|
||||
// So now we have the ability to dump "aud" as a string if there's
|
||||
// only one entry, but we need to retain the old behavior so that
|
||||
// we don't accidentally break somebody else's code. (e.g. messing
|
||||
// up how signatures are calculated)
|
||||
var FlattenAudience uint32
|
||||
|
||||
func MarshalAudience(aud []string, flatten bool) ([]byte, error) {
|
||||
var val any
|
||||
if len(aud) == 1 && flatten {
|
||||
val = aud[0]
|
||||
} else {
|
||||
val = aud
|
||||
}
|
||||
return Marshal(val)
|
||||
}
|
||||
|
||||
func EncodeAudience(enc *Encoder, aud []string, flatten bool) error {
|
||||
var val any
|
||||
if len(aud) == 1 && flatten {
|
||||
val = aud[0]
|
||||
} else {
|
||||
val = aud
|
||||
}
|
||||
return enc.Encode(val)
|
||||
}
|
||||
|
||||
// DecodeCtx is an interface for objects that needs that extra something
|
||||
// when decoding JSON into an object.
|
||||
type DecodeCtx interface {
|
||||
Registry() *Registry
|
||||
}
|
||||
|
||||
// DecodeCtxContainer is used to differentiate objects that can carry extra
|
||||
// decoding hints and those who can't.
|
||||
type DecodeCtxContainer interface {
|
||||
DecodeCtx() DecodeCtx
|
||||
SetDecodeCtx(DecodeCtx)
|
||||
}
|
||||
|
||||
// stock decodeCtx. should cover 80% of the cases
|
||||
type decodeCtx struct {
|
||||
registry *Registry
|
||||
}
|
||||
|
||||
func NewDecodeCtx(r *Registry) DecodeCtx {
|
||||
return &decodeCtx{registry: r}
|
||||
}
|
||||
|
||||
func (dc *decodeCtx) Registry() *Registry {
|
||||
return dc.registry
|
||||
}
|
||||
|
||||
func Dump(v any) {
|
||||
enc := NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
//nolint:errchkjson
|
||||
_ = enc.Encode(v)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CustomDecoder is the interface we expect from RegisterCustomField in jws, jwe, jwk, and jwt packages.
|
||||
type CustomDecoder interface {
|
||||
// Decode takes a JSON encoded byte slice and returns the desired
|
||||
// decoded value,which will be used as the value for that field
|
||||
// registered through RegisterCustomField
|
||||
Decode([]byte) (any, error)
|
||||
}
|
||||
|
||||
// CustomDecodeFunc is a stateless, function-based implementation of CustomDecoder
|
||||
type CustomDecodeFunc func([]byte) (any, error)
|
||||
|
||||
func (fn CustomDecodeFunc) Decode(data []byte) (any, error) {
|
||||
return fn(data)
|
||||
}
|
||||
|
||||
type objectTypeDecoder struct {
|
||||
typ reflect.Type
|
||||
name string
|
||||
}
|
||||
|
||||
func (dec *objectTypeDecoder) Decode(data []byte) (any, error) {
|
||||
ptr := reflect.New(dec.typ).Interface()
|
||||
if err := Unmarshal(data, ptr); err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode field %s: %w`, dec.name, err)
|
||||
}
|
||||
return reflect.ValueOf(ptr).Elem().Interface(), nil
|
||||
}
|
||||
|
||||
type Registry struct {
|
||||
mu *sync.RWMutex
|
||||
ctrs map[string]CustomDecoder
|
||||
}
|
||||
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
mu: &sync.RWMutex{},
|
||||
ctrs: make(map[string]CustomDecoder),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) Register(name string, object any) {
|
||||
if object == nil {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
delete(r.ctrs, name)
|
||||
return
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if ctr, ok := object.(CustomDecoder); ok {
|
||||
r.ctrs[name] = ctr
|
||||
} else {
|
||||
r.ctrs[name] = &objectTypeDecoder{
|
||||
typ: reflect.TypeOf(object),
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Registry) Decode(dec *Decoder, name string) (any, error) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
if ctr, ok := r.ctrs[name]; ok {
|
||||
var raw RawMessage
|
||||
if err := dec.Decode(&raw); err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err)
|
||||
}
|
||||
v, err := ctr.Decode([]byte(raw))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err)
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
var decoded any
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode field %s: %w`, name, err)
|
||||
}
|
||||
return decoded, nil
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
//go:build !jwx_goccy
|
||||
|
||||
//nolint:revive
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Decoder = json.Decoder
|
||||
type Delim = json.Delim
|
||||
type Encoder = json.Encoder
|
||||
type Marshaler = json.Marshaler
|
||||
type Number = json.Number
|
||||
type RawMessage = json.RawMessage
|
||||
type Unmarshaler = json.Unmarshaler
|
||||
|
||||
func Engine() string {
|
||||
return "encoding/json"
|
||||
}
|
||||
|
||||
// NewDecoder respects the values specified in DecoderSettings,
|
||||
// and creates a Decoder that has certain features turned on/off
|
||||
func NewDecoder(r io.Reader) *json.Decoder {
|
||||
dec := json.NewDecoder(r)
|
||||
|
||||
if UseNumber() {
|
||||
dec.UseNumber()
|
||||
}
|
||||
|
||||
return dec
|
||||
}
|
||||
|
||||
func NewEncoder(w io.Writer) *json.Encoder {
|
||||
return json.NewEncoder(w)
|
||||
}
|
||||
|
||||
// Marshal is just a proxy for "encoding/json".Marshal
|
||||
func Marshal(v any) ([]byte, error) {
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
// MarshalIndent is just a proxy for "encoding/json".MarshalIndent
|
||||
func MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||
return json.MarshalIndent(v, prefix, indent)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "jwxio",
|
||||
srcs = ["jwxio.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/jwxio",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package jwxio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errNonFiniteSource = errors.New(`cannot read from non-finite source`)
|
||||
|
||||
func NonFiniteSourceError() error {
|
||||
return errNonFiniteSource
|
||||
}
|
||||
|
||||
// ReadAllFromFiniteSource reads all data from a io.Reader _if_ it comes from a
|
||||
// finite source.
|
||||
func ReadAllFromFiniteSource(rdr io.Reader) ([]byte, error) {
|
||||
switch rdr.(type) {
|
||||
case *bytes.Reader, *bytes.Buffer, *strings.Reader:
|
||||
data, err := io.ReadAll(rdr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
default:
|
||||
return nil, errNonFiniteSource
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "keyconv",
|
||||
srcs = ["keyconv.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/keyconv",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//jwk",
|
||||
"@com_github_lestrrat_go_blackmagic//:blackmagic",
|
||||
"@org_golang_x_crypto//ed25519",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "keyconv_test",
|
||||
srcs = ["keyconv_test.go"],
|
||||
deps = [
|
||||
":keyconv",
|
||||
"//internal/jwxtest",
|
||||
"//jwa",
|
||||
"//jwk",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":keyconv",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
package keyconv
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/lestrrat-go/blackmagic"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
// RSAPrivateKey assigns src to dst.
|
||||
// `dst` should be a pointer to a rsa.PrivateKey.
|
||||
// `src` may be rsa.PrivateKey, *rsa.PrivateKey, or a jwk.Key
|
||||
func RSAPrivateKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var raw rsa.PrivateKey
|
||||
if err := jwk.Export(jwkKey, &raw); err != nil {
|
||||
return fmt.Errorf(`failed to produce rsa.PrivateKey from %T: %w`, src, err)
|
||||
}
|
||||
src = &raw
|
||||
}
|
||||
|
||||
var ptr *rsa.PrivateKey
|
||||
switch src := src.(type) {
|
||||
case rsa.PrivateKey:
|
||||
ptr = &src
|
||||
case *rsa.PrivateKey:
|
||||
ptr = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected rsa.PrivateKey or *rsa.PrivateKey, got %T`, src)
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
// RSAPublicKey assigns src to dst
|
||||
// `dst` should be a pointer to a non-zero rsa.PublicKey.
|
||||
// `src` may be rsa.PublicKey, *rsa.PublicKey, or a jwk.Key
|
||||
func RSAPublicKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
pk, err := jwk.PublicRawKeyOf(jwkKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err)
|
||||
}
|
||||
src = pk
|
||||
}
|
||||
|
||||
var ptr *rsa.PublicKey
|
||||
switch src := src.(type) {
|
||||
case rsa.PrivateKey:
|
||||
ptr = &src.PublicKey
|
||||
case *rsa.PrivateKey:
|
||||
ptr = &src.PublicKey
|
||||
case rsa.PublicKey:
|
||||
ptr = &src
|
||||
case *rsa.PublicKey:
|
||||
ptr = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected rsa.PublicKey/rsa.PrivateKey or *rsa.PublicKey/*rsa.PrivateKey, got %T`, src)
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
// ECDSAPrivateKey assigns src to dst, converting its type from a
|
||||
// non-pointer to a pointer
|
||||
func ECDSAPrivateKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var raw ecdsa.PrivateKey
|
||||
if err := jwk.Export(jwkKey, &raw); err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce ecdsa.PrivateKey from %T: %w`, src, err)
|
||||
}
|
||||
src = &raw
|
||||
}
|
||||
|
||||
var ptr *ecdsa.PrivateKey
|
||||
switch src := src.(type) {
|
||||
case ecdsa.PrivateKey:
|
||||
ptr = &src
|
||||
case *ecdsa.PrivateKey:
|
||||
ptr = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected ecdsa.PrivateKey or *ecdsa.PrivateKey, got %T`, src)
|
||||
}
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
// ECDSAPublicKey assigns src to dst, converting its type from a
|
||||
// non-pointer to a pointer
|
||||
func ECDSAPublicKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
pk, err := jwk.PublicRawKeyOf(jwkKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err)
|
||||
}
|
||||
src = pk
|
||||
}
|
||||
|
||||
var ptr *ecdsa.PublicKey
|
||||
switch src := src.(type) {
|
||||
case ecdsa.PrivateKey:
|
||||
ptr = &src.PublicKey
|
||||
case *ecdsa.PrivateKey:
|
||||
ptr = &src.PublicKey
|
||||
case ecdsa.PublicKey:
|
||||
ptr = &src
|
||||
case *ecdsa.PublicKey:
|
||||
ptr = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected ecdsa.PublicKey/ecdsa.PrivateKey or *ecdsa.PublicKey/*ecdsa.PrivateKey, got %T`, src)
|
||||
}
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
func ByteSliceKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var raw []byte
|
||||
if err := jwk.Export(jwkKey, &raw); err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce []byte from %T: %w`, src, err)
|
||||
}
|
||||
src = raw
|
||||
}
|
||||
|
||||
if _, ok := src.([]byte); !ok {
|
||||
return fmt.Errorf(`keyconv: expected []byte, got %T`, src)
|
||||
}
|
||||
return blackmagic.AssignIfCompatible(dst, src)
|
||||
}
|
||||
|
||||
func Ed25519PrivateKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var raw ed25519.PrivateKey
|
||||
if err := jwk.Export(jwkKey, &raw); err != nil {
|
||||
return fmt.Errorf(`failed to produce ed25519.PrivateKey from %T: %w`, src, err)
|
||||
}
|
||||
src = &raw
|
||||
}
|
||||
|
||||
var ptr *ed25519.PrivateKey
|
||||
switch src := src.(type) {
|
||||
case ed25519.PrivateKey:
|
||||
ptr = &src
|
||||
case *ed25519.PrivateKey:
|
||||
ptr = src
|
||||
default:
|
||||
return fmt.Errorf(`expected ed25519.PrivateKey or *ed25519.PrivateKey, got %T`, src)
|
||||
}
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
func Ed25519PublicKey(dst, src any) error {
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
pk, err := jwk.PublicRawKeyOf(jwkKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce public key from %T: %w`, src, err)
|
||||
}
|
||||
src = pk
|
||||
}
|
||||
|
||||
switch key := src.(type) {
|
||||
case ed25519.PrivateKey:
|
||||
src = key.Public()
|
||||
case *ed25519.PrivateKey:
|
||||
src = key.Public()
|
||||
}
|
||||
|
||||
var ptr *ed25519.PublicKey
|
||||
switch src := src.(type) {
|
||||
case ed25519.PublicKey:
|
||||
ptr = &src
|
||||
case *ed25519.PublicKey:
|
||||
ptr = src
|
||||
case *crypto.PublicKey:
|
||||
tmp, ok := (*src).(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of *crypto.PublicKey`)
|
||||
}
|
||||
ptr = &tmp
|
||||
case crypto.PublicKey:
|
||||
tmp, ok := src.(ed25519.PublicKey)
|
||||
if !ok {
|
||||
return fmt.Errorf(`failed to retrieve ed25519.PublicKey out of crypto.PublicKey`)
|
||||
}
|
||||
ptr = &tmp
|
||||
default:
|
||||
return fmt.Errorf(`expected ed25519.PublicKey or *ed25519.PublicKey, got %T`, src)
|
||||
}
|
||||
return blackmagic.AssignIfCompatible(dst, ptr)
|
||||
}
|
||||
|
||||
type privECDHer interface {
|
||||
ECDH() (*ecdh.PrivateKey, error)
|
||||
}
|
||||
|
||||
func ECDHPrivateKey(dst, src any) error {
|
||||
var privECDH *ecdh.PrivateKey
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var rawECDH ecdh.PrivateKey
|
||||
if err := jwk.Export(jwkKey, &rawECDH); err == nil {
|
||||
privECDH = &rawECDH
|
||||
} else {
|
||||
// If we cannot export the key as an ecdh.PrivateKey, we try to export it as an ecdsa.PrivateKey
|
||||
var rawECDSA ecdsa.PrivateKey
|
||||
if err := jwk.Export(jwkKey, &rawECDSA); err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce ecdh.PrivateKey or ecdsa.PrivateKey from %T: %w`, src, err)
|
||||
}
|
||||
src = &rawECDSA
|
||||
}
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case ecdh.PrivateKey:
|
||||
privECDH = &src
|
||||
case *ecdh.PrivateKey:
|
||||
privECDH = src
|
||||
case privECDHer:
|
||||
priv, err := src.ECDH()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to convert ecdsa.PrivateKey to ecdh.PrivateKey: %w`, err)
|
||||
}
|
||||
privECDH = priv
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, privECDH)
|
||||
}
|
||||
|
||||
type pubECDHer interface {
|
||||
ECDH() (*ecdh.PublicKey, error)
|
||||
}
|
||||
|
||||
func ECDHPublicKey(dst, src any) error {
|
||||
var pubECDH *ecdh.PublicKey
|
||||
if jwkKey, ok := src.(jwk.Key); ok {
|
||||
var rawECDH ecdh.PublicKey
|
||||
if err := jwk.Export(jwkKey, &rawECDH); err == nil {
|
||||
pubECDH = &rawECDH
|
||||
} else {
|
||||
// If we cannot export the key as an ecdh.PublicKey, we try to export it as an ecdsa.PublicKey
|
||||
var rawECDSA ecdsa.PublicKey
|
||||
if err := jwk.Export(jwkKey, &rawECDSA); err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to produce ecdh.PublicKey or ecdsa.PublicKey from %T: %w`, src, err)
|
||||
}
|
||||
src = &rawECDSA
|
||||
}
|
||||
}
|
||||
|
||||
switch src := src.(type) {
|
||||
case ecdh.PublicKey:
|
||||
pubECDH = &src
|
||||
case *ecdh.PublicKey:
|
||||
pubECDH = src
|
||||
case pubECDHer:
|
||||
pub, err := src.ECDH()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv: failed to convert ecdsa.PublicKey to ecdh.PublicKey: %w`, err)
|
||||
}
|
||||
pubECDH = pub
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, pubECDH)
|
||||
}
|
||||
|
||||
// ecdhCurveToElliptic maps ECDH curves to elliptic curves
|
||||
func ecdhCurveToElliptic(ecdhCurve ecdh.Curve) (elliptic.Curve, error) {
|
||||
switch ecdhCurve {
|
||||
case ecdh.P256():
|
||||
return elliptic.P256(), nil
|
||||
case ecdh.P384():
|
||||
return elliptic.P384(), nil
|
||||
case ecdh.P521():
|
||||
return elliptic.P521(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`keyconv: unsupported ECDH curve: %v`, ecdhCurve)
|
||||
}
|
||||
}
|
||||
|
||||
// ecdhPublicKeyToECDSA converts an ECDH public key to an ECDSA public key
|
||||
func ecdhPublicKeyToECDSA(ecdhPubKey *ecdh.PublicKey) (*ecdsa.PublicKey, error) {
|
||||
curve, err := ecdhCurveToElliptic(ecdhPubKey.Curve())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubBytes := ecdhPubKey.Bytes()
|
||||
|
||||
// Parse the uncompressed point format (0x04 prefix + X + Y coordinates)
|
||||
if len(pubBytes) == 0 || pubBytes[0] != 0x04 {
|
||||
return nil, fmt.Errorf(`keyconv: invalid ECDH public key format`)
|
||||
}
|
||||
|
||||
keyLen := (len(pubBytes) - 1) / 2
|
||||
if len(pubBytes) != 1+2*keyLen {
|
||||
return nil, fmt.Errorf(`keyconv: invalid ECDH public key length`)
|
||||
}
|
||||
|
||||
x := new(big.Int).SetBytes(pubBytes[1 : 1+keyLen])
|
||||
y := new(big.Int).SetBytes(pubBytes[1+keyLen:])
|
||||
|
||||
return &ecdsa.PublicKey{
|
||||
Curve: curve,
|
||||
X: x,
|
||||
Y: y,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ECDHToECDSA(dst, src any) error {
|
||||
// convert ecdh.PublicKey to ecdsa.PublicKey, ecdh.PrivateKey to ecdsa.PrivateKey
|
||||
|
||||
// First, handle value types by converting to pointers
|
||||
switch s := src.(type) {
|
||||
case ecdh.PrivateKey:
|
||||
src = &s
|
||||
case ecdh.PublicKey:
|
||||
src = &s
|
||||
}
|
||||
|
||||
var privBytes []byte
|
||||
var pubkey *ecdh.PublicKey
|
||||
// Now handle the actual conversion with pointer types
|
||||
switch src := src.(type) {
|
||||
case *ecdh.PrivateKey:
|
||||
pubkey = src.PublicKey()
|
||||
privBytes = src.Bytes()
|
||||
case *ecdh.PublicKey:
|
||||
pubkey = src
|
||||
default:
|
||||
return fmt.Errorf(`keyconv: expected ecdh.PrivateKey, *ecdh.PrivateKey, ecdh.PublicKey, or *ecdh.PublicKey, got %T`, src)
|
||||
}
|
||||
|
||||
// convert the public key
|
||||
ecdsaPubKey, err := ecdhPublicKeyToECDSA(pubkey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`keyconv.ECDHToECDSA: failed to convert ECDH public key to ECDSA public key: %w`, err)
|
||||
}
|
||||
|
||||
// return if we were being asked to convert *ecdh.PublicKey
|
||||
if privBytes == nil {
|
||||
return blackmagic.AssignIfCompatible(dst, ecdsaPubKey)
|
||||
}
|
||||
|
||||
// Then create the private key with the public key embedded
|
||||
ecdsaPrivKey := &ecdsa.PrivateKey{
|
||||
D: new(big.Int).SetBytes(privBytes),
|
||||
PublicKey: *ecdsaPubKey,
|
||||
}
|
||||
|
||||
return blackmagic.AssignIfCompatible(dst, ecdsaPrivKey)
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "pool",
|
||||
srcs = [
|
||||
"big_int.go",
|
||||
"byte_slice.go",
|
||||
"bytes_buffer.go",
|
||||
"error_slice.go",
|
||||
"key_to_error_map.go",
|
||||
"pool.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/pool",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":pool",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "pool_test",
|
||||
srcs = [
|
||||
"byte_slice_test.go",
|
||||
],
|
||||
deps = [
|
||||
":pool",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package pool
|
||||
|
||||
import "math/big"
|
||||
|
||||
var bigIntPool = New[*big.Int](allocBigInt, freeBigInt)
|
||||
|
||||
func allocBigInt() *big.Int {
|
||||
return &big.Int{}
|
||||
}
|
||||
|
||||
func freeBigInt(b *big.Int) *big.Int {
|
||||
b.SetInt64(0) // Reset the value to zero
|
||||
return b
|
||||
}
|
||||
|
||||
// BigInt returns a pool of *big.Int instances.
|
||||
func BigInt() *Pool[*big.Int] {
|
||||
return bigIntPool
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package pool
|
||||
|
||||
var byteSlicePool = SlicePool[byte]{
|
||||
pool: New[[]byte](allocByteSlice, freeByteSlice),
|
||||
}
|
||||
|
||||
func allocByteSlice() []byte {
|
||||
return make([]byte, 0, 64) // Default capacity of 64 bytes
|
||||
}
|
||||
|
||||
func freeByteSlice(b []byte) []byte {
|
||||
clear(b)
|
||||
b = b[:0] // Reset the slice to zero length
|
||||
return b
|
||||
}
|
||||
|
||||
func ByteSlice() SlicePool[byte] {
|
||||
return byteSlicePool
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package pool
|
||||
|
||||
import "bytes"
|
||||
|
||||
var bytesBufferPool = New[*bytes.Buffer](allocBytesBuffer, freeBytesBuffer)
|
||||
|
||||
func allocBytesBuffer() *bytes.Buffer {
|
||||
return &bytes.Buffer{}
|
||||
}
|
||||
|
||||
func freeBytesBuffer(b *bytes.Buffer) *bytes.Buffer {
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
func BytesBuffer() *Pool[*bytes.Buffer] {
|
||||
return bytesBufferPool
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package pool
|
||||
|
||||
var errorSlicePool = New[[]error](allocErrorSlice, freeErrorSlice)
|
||||
|
||||
func allocErrorSlice() []error {
|
||||
return make([]error, 0, 1)
|
||||
}
|
||||
|
||||
func freeErrorSlice(s []error) []error {
|
||||
// Reset the slice to its zero value
|
||||
return s[:0]
|
||||
}
|
||||
|
||||
func ErrorSlice() *Pool[[]error] {
|
||||
return errorSlicePool
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package pool
|
||||
|
||||
var keyToErrorMapPool = New[map[string]error](allocKeyToErrorMap, freeKeyToErrorMap)
|
||||
|
||||
func allocKeyToErrorMap() map[string]error {
|
||||
return make(map[string]error)
|
||||
}
|
||||
|
||||
func freeKeyToErrorMap(m map[string]error) map[string]error {
|
||||
for k := range m {
|
||||
delete(m, k) // Clear the map
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// KeyToErrorMap returns a pool of map[string]error instances.
|
||||
func KeyToErrorMap() *Pool[map[string]error] {
|
||||
return keyToErrorMapPool
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Pool[T any] struct {
|
||||
pool sync.Pool
|
||||
destructor func(T) T
|
||||
}
|
||||
|
||||
// New creates a new Pool instance for the type T.
|
||||
// The allocator function is used to create new instances of T when the pool is empty.
|
||||
// The destructor function is used to clean up instances of T before they are returned to the pool.
|
||||
// The destructor should reset the state of T to a clean state, so it can be reused, and
|
||||
// return the modified instance of T. This is required for cases when you reset operations
|
||||
// can modify the underlying data structure, such as slices or maps.
|
||||
func New[T any](allocator func() T, destructor func(T) T) *Pool[T] {
|
||||
return &Pool[T]{
|
||||
pool: sync.Pool{
|
||||
New: func() any {
|
||||
return allocator()
|
||||
},
|
||||
},
|
||||
destructor: destructor,
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves an item of type T from the pool.
|
||||
func (p *Pool[T]) Get() T {
|
||||
//nolint:forcetypeassert
|
||||
return p.pool.Get().(T)
|
||||
}
|
||||
|
||||
// Put returns an item of type T to the pool.
|
||||
// The item is first processed by the destructor function to ensure it is in a clean state.
|
||||
func (p *Pool[T]) Put(item T) {
|
||||
p.pool.Put(p.destructor(item))
|
||||
}
|
||||
|
||||
// SlicePool is a specialized pool for slices of type T. It is identical to Pool[T] but
|
||||
// provides additional functionality to get slices with a specific capacity.
|
||||
type SlicePool[T any] struct {
|
||||
pool *Pool[[]T]
|
||||
}
|
||||
|
||||
func NewSlicePool[T any](allocator func() []T, destructor func([]T) []T) SlicePool[T] {
|
||||
return SlicePool[T]{
|
||||
pool: New(allocator, destructor),
|
||||
}
|
||||
}
|
||||
|
||||
func (p SlicePool[T]) Get() []T {
|
||||
return p.pool.Get()
|
||||
}
|
||||
|
||||
func (p SlicePool[T]) GetCapacity(capacity int) []T {
|
||||
if capacity <= 0 {
|
||||
return p.Get()
|
||||
}
|
||||
s := p.Get()
|
||||
if cap(s) < capacity {
|
||||
s = make([]T, 0, capacity)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p SlicePool[T]) Put(s []T) {
|
||||
p.pool.Put(s)
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "tokens",
|
||||
srcs = [
|
||||
"jwe_tokens.go",
|
||||
"tokens.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/internal/tokens",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":tokens",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package tokens
|
||||
|
||||
// JWE Key Encryption Algorithms
|
||||
const (
|
||||
// RSA algorithms
|
||||
RSA1_5 = "RSA1_5"
|
||||
RSA_OAEP = "RSA-OAEP"
|
||||
RSA_OAEP_256 = "RSA-OAEP-256"
|
||||
RSA_OAEP_384 = "RSA-OAEP-384"
|
||||
RSA_OAEP_512 = "RSA-OAEP-512"
|
||||
|
||||
// AES Key Wrap algorithms
|
||||
A128KW = "A128KW"
|
||||
A192KW = "A192KW"
|
||||
A256KW = "A256KW"
|
||||
|
||||
// AES GCM Key Wrap algorithms
|
||||
A128GCMKW = "A128GCMKW"
|
||||
A192GCMKW = "A192GCMKW"
|
||||
A256GCMKW = "A256GCMKW"
|
||||
|
||||
// ECDH-ES algorithms
|
||||
ECDH_ES = "ECDH-ES"
|
||||
ECDH_ES_A128KW = "ECDH-ES+A128KW"
|
||||
ECDH_ES_A192KW = "ECDH-ES+A192KW"
|
||||
ECDH_ES_A256KW = "ECDH-ES+A256KW"
|
||||
|
||||
// PBES2 algorithms
|
||||
PBES2_HS256_A128KW = "PBES2-HS256+A128KW"
|
||||
PBES2_HS384_A192KW = "PBES2-HS384+A192KW"
|
||||
PBES2_HS512_A256KW = "PBES2-HS512+A256KW"
|
||||
|
||||
// Direct key agreement
|
||||
DIRECT = "dir"
|
||||
)
|
||||
|
||||
// JWE Content Encryption Algorithms
|
||||
const (
|
||||
// AES GCM algorithms
|
||||
A128GCM = "A128GCM"
|
||||
A192GCM = "A192GCM"
|
||||
A256GCM = "A256GCM"
|
||||
|
||||
// AES CBC + HMAC algorithms
|
||||
A128CBC_HS256 = "A128CBC-HS256"
|
||||
A192CBC_HS384 = "A192CBC-HS384"
|
||||
A256CBC_HS512 = "A256CBC-HS512"
|
||||
)
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package tokens
|
||||
|
||||
const (
|
||||
CloseCurlyBracket = '}'
|
||||
CloseSquareBracket = ']'
|
||||
Colon = ':'
|
||||
Comma = ','
|
||||
DoubleQuote = '"'
|
||||
OpenCurlyBracket = '{'
|
||||
OpenSquareBracket = '['
|
||||
Period = '.'
|
||||
)
|
||||
|
||||
// Cryptographic key sizes
|
||||
const (
|
||||
KeySize16 = 16
|
||||
KeySize24 = 24
|
||||
KeySize32 = 32
|
||||
KeySize48 = 48 // A192CBC_HS384 key size
|
||||
KeySize64 = 64 // A256CBC_HS512 key size
|
||||
)
|
||||
|
||||
// Bit/byte conversion factors
|
||||
const (
|
||||
BitsPerByte = 8
|
||||
BytesPerBit = 1.0 / 8
|
||||
)
|
||||
|
||||
// Key wrapping constants
|
||||
const (
|
||||
KeywrapChunkLen = 8
|
||||
KeywrapRounds = 6 // RFC 3394 key wrap rounds
|
||||
KeywrapBlockSize = 8 // Key wrap block size in bytes
|
||||
)
|
||||
|
||||
// AES-GCM constants
|
||||
const (
|
||||
GCMIVSize = 12 // GCM IV size in bytes (96 bits)
|
||||
GCMTagSize = 16 // GCM tag size in bytes (128 bits)
|
||||
)
|
||||
|
||||
// PBES2 constants
|
||||
const (
|
||||
PBES2DefaultIterations = 10000 // Default PBKDF2 iteration count
|
||||
PBES2NullByteSeparator = 0 // Null byte separator for PBES2
|
||||
)
|
||||
|
||||
// RSA key generation constants
|
||||
const (
|
||||
RSAKeyGenMultiplier = 2 // RSA key generation size multiplier
|
||||
)
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "jwa",
|
||||
srcs = [
|
||||
"compression_gen.go",
|
||||
"content_encryption_gen.go",
|
||||
"elliptic_gen.go",
|
||||
"jwa.go",
|
||||
"key_encryption_gen.go",
|
||||
"key_type_gen.go",
|
||||
"options_gen.go",
|
||||
"signature_gen.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwa",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/tokens",
|
||||
"@com_github_lestrrat_go_option_v2//:option",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwa_test",
|
||||
srcs = [
|
||||
"compression_gen_test.go",
|
||||
"content_encryption_gen_test.go",
|
||||
"elliptic_gen_test.go",
|
||||
"jwa_test.go",
|
||||
"key_encryption_gen_test.go",
|
||||
"key_type_gen_test.go",
|
||||
"signature_gen_test.go",
|
||||
],
|
||||
deps = [
|
||||
":jwa",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@com_github_lestrrat_go_option_v2//:option",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":jwa",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# JWA [](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwa)
|
||||
|
||||
Package [github.com/lestrrat-go/jwx/v3/jwa](./jwa) defines the various algorithm described in [RFC7518](https://tools.ietf.org/html/rfc7518)
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var muAllCompressionAlgorithm sync.RWMutex
|
||||
var allCompressionAlgorithm = map[string]CompressionAlgorithm{}
|
||||
var muListCompressionAlgorithm sync.RWMutex
|
||||
var listCompressionAlgorithm []CompressionAlgorithm
|
||||
var builtinCompressionAlgorithm = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for CompressionAlgorithm
|
||||
algorithms := make([]CompressionAlgorithm, 2)
|
||||
algorithms[0] = NewCompressionAlgorithm("DEF")
|
||||
algorithms[1] = NewCompressionAlgorithm("")
|
||||
|
||||
RegisterCompressionAlgorithm(algorithms...)
|
||||
}
|
||||
|
||||
// Deflate returns an object representing the "DEF" content compression algorithm value. Using this value specifies that the content should be compressed using DEFLATE (RFC 1951).
|
||||
func Deflate() CompressionAlgorithm {
|
||||
return lookupBuiltinCompressionAlgorithm("DEF")
|
||||
}
|
||||
|
||||
// NoCompress returns an object representing an empty compression algorithm value. Using this value specifies that the content should not be compressed.
|
||||
func NoCompress() CompressionAlgorithm {
|
||||
return lookupBuiltinCompressionAlgorithm("")
|
||||
}
|
||||
|
||||
func lookupBuiltinCompressionAlgorithm(name string) CompressionAlgorithm {
|
||||
muAllCompressionAlgorithm.RLock()
|
||||
v, ok := allCompressionAlgorithm[name]
|
||||
muAllCompressionAlgorithm.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: CompressionAlgorithm %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// CompressionAlgorithm represents the compression algorithms as described in https://tools.ietf.org/html/rfc7518#section-7.3
|
||||
type CompressionAlgorithm struct {
|
||||
name string
|
||||
deprecated bool
|
||||
}
|
||||
|
||||
func (s CompressionAlgorithm) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the CompressionAlgorithm object is deprecated.
|
||||
func (s CompressionAlgorithm) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// EmptyCompressionAlgorithm returns an empty CompressionAlgorithm object, used as a zero value.
|
||||
func EmptyCompressionAlgorithm() CompressionAlgorithm {
|
||||
return CompressionAlgorithm{}
|
||||
}
|
||||
|
||||
// NewCompressionAlgorithm creates a new CompressionAlgorithm object with the given name.
|
||||
func NewCompressionAlgorithm(name string, options ...NewAlgorithmOption) CompressionAlgorithm {
|
||||
var deprecated bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewCompressionAlgorithm: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return CompressionAlgorithm{name: name, deprecated: deprecated}
|
||||
}
|
||||
|
||||
// LookupCompressionAlgorithm returns the CompressionAlgorithm object for the given name.
|
||||
func LookupCompressionAlgorithm(name string) (CompressionAlgorithm, bool) {
|
||||
muAllCompressionAlgorithm.RLock()
|
||||
v, ok := allCompressionAlgorithm[name]
|
||||
muAllCompressionAlgorithm.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterCompressionAlgorithm registers a new CompressionAlgorithm. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterCompressionAlgorithm(algorithms ...CompressionAlgorithm) {
|
||||
muAllCompressionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allCompressionAlgorithm[alg.String()] = alg
|
||||
}
|
||||
muAllCompressionAlgorithm.Unlock()
|
||||
rebuildCompressionAlgorithm()
|
||||
}
|
||||
|
||||
// UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterCompressionAlgorithm(algorithms ...CompressionAlgorithm) {
|
||||
muAllCompressionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinCompressionAlgorithm[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allCompressionAlgorithm, alg.String())
|
||||
}
|
||||
muAllCompressionAlgorithm.Unlock()
|
||||
rebuildCompressionAlgorithm()
|
||||
}
|
||||
|
||||
func rebuildCompressionAlgorithm() {
|
||||
list := make([]CompressionAlgorithm, 0, len(allCompressionAlgorithm))
|
||||
muAllCompressionAlgorithm.RLock()
|
||||
for _, v := range allCompressionAlgorithm {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllCompressionAlgorithm.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListCompressionAlgorithm.Lock()
|
||||
listCompressionAlgorithm = list
|
||||
muListCompressionAlgorithm.Unlock()
|
||||
}
|
||||
|
||||
// CompressionAlgorithms returns a list of all available values for CompressionAlgorithm.
|
||||
func CompressionAlgorithms() []CompressionAlgorithm {
|
||||
muListCompressionAlgorithm.RLock()
|
||||
defer muListCompressionAlgorithm.RUnlock()
|
||||
return listCompressionAlgorithm
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the CompressionAlgorithm object to a JSON string.
|
||||
func (s CompressionAlgorithm) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a CompressionAlgorithm object.
|
||||
func (s *CompressionAlgorithm) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal CompressionAlgorithm: %w`, err)
|
||||
}
|
||||
v, ok := LookupCompressionAlgorithm(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown CompressionAlgorithm: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
var muAllContentEncryptionAlgorithm sync.RWMutex
|
||||
var allContentEncryptionAlgorithm = map[string]ContentEncryptionAlgorithm{}
|
||||
var muListContentEncryptionAlgorithm sync.RWMutex
|
||||
var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm
|
||||
var builtinContentEncryptionAlgorithm = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for ContentEncryptionAlgorithm
|
||||
algorithms := make([]ContentEncryptionAlgorithm, 6)
|
||||
algorithms[0] = NewContentEncryptionAlgorithm(tokens.A128CBC_HS256)
|
||||
algorithms[1] = NewContentEncryptionAlgorithm(tokens.A128GCM)
|
||||
algorithms[2] = NewContentEncryptionAlgorithm(tokens.A192CBC_HS384)
|
||||
algorithms[3] = NewContentEncryptionAlgorithm(tokens.A192GCM)
|
||||
algorithms[4] = NewContentEncryptionAlgorithm(tokens.A256CBC_HS512)
|
||||
algorithms[5] = NewContentEncryptionAlgorithm(tokens.A256GCM)
|
||||
|
||||
RegisterContentEncryptionAlgorithm(algorithms...)
|
||||
}
|
||||
|
||||
// A128CBC_HS256 returns an object representing A128CBC-HS256. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA256 (128).
|
||||
func A128CBC_HS256() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A128CBC_HS256)
|
||||
}
|
||||
|
||||
// A128GCM returns an object representing A128GCM. Using this value specifies that the content should be encrypted using AES-GCM (128).
|
||||
func A128GCM() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A128GCM)
|
||||
}
|
||||
|
||||
// A192CBC_HS384 returns an object representing A192CBC-HS384. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA384 (192).
|
||||
func A192CBC_HS384() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A192CBC_HS384)
|
||||
}
|
||||
|
||||
// A192GCM returns an object representing A192GCM. Using this value specifies that the content should be encrypted using AES-GCM (192).
|
||||
func A192GCM() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A192GCM)
|
||||
}
|
||||
|
||||
// A256CBC_HS512 returns an object representing A256CBC-HS512. Using this value specifies that the content should be encrypted using AES-CBC + HMAC-SHA512 (256).
|
||||
func A256CBC_HS512() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A256CBC_HS512)
|
||||
}
|
||||
|
||||
// A256GCM returns an object representing A256GCM. Using this value specifies that the content should be encrypted using AES-GCM (256).
|
||||
func A256GCM() ContentEncryptionAlgorithm {
|
||||
return lookupBuiltinContentEncryptionAlgorithm(tokens.A256GCM)
|
||||
}
|
||||
|
||||
func lookupBuiltinContentEncryptionAlgorithm(name string) ContentEncryptionAlgorithm {
|
||||
muAllContentEncryptionAlgorithm.RLock()
|
||||
v, ok := allContentEncryptionAlgorithm[name]
|
||||
muAllContentEncryptionAlgorithm.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: ContentEncryptionAlgorithm %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ContentEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-5
|
||||
type ContentEncryptionAlgorithm struct {
|
||||
name string
|
||||
deprecated bool
|
||||
}
|
||||
|
||||
func (s ContentEncryptionAlgorithm) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the ContentEncryptionAlgorithm object is deprecated.
|
||||
func (s ContentEncryptionAlgorithm) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// EmptyContentEncryptionAlgorithm returns an empty ContentEncryptionAlgorithm object, used as a zero value.
|
||||
func EmptyContentEncryptionAlgorithm() ContentEncryptionAlgorithm {
|
||||
return ContentEncryptionAlgorithm{}
|
||||
}
|
||||
|
||||
// NewContentEncryptionAlgorithm creates a new ContentEncryptionAlgorithm object with the given name.
|
||||
func NewContentEncryptionAlgorithm(name string, options ...NewAlgorithmOption) ContentEncryptionAlgorithm {
|
||||
var deprecated bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewContentEncryptionAlgorithm: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return ContentEncryptionAlgorithm{name: name, deprecated: deprecated}
|
||||
}
|
||||
|
||||
// LookupContentEncryptionAlgorithm returns the ContentEncryptionAlgorithm object for the given name.
|
||||
func LookupContentEncryptionAlgorithm(name string) (ContentEncryptionAlgorithm, bool) {
|
||||
muAllContentEncryptionAlgorithm.RLock()
|
||||
v, ok := allContentEncryptionAlgorithm[name]
|
||||
muAllContentEncryptionAlgorithm.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterContentEncryptionAlgorithm registers a new ContentEncryptionAlgorithm. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterContentEncryptionAlgorithm(algorithms ...ContentEncryptionAlgorithm) {
|
||||
muAllContentEncryptionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allContentEncryptionAlgorithm[alg.String()] = alg
|
||||
}
|
||||
muAllContentEncryptionAlgorithm.Unlock()
|
||||
rebuildContentEncryptionAlgorithm()
|
||||
}
|
||||
|
||||
// UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterContentEncryptionAlgorithm(algorithms ...ContentEncryptionAlgorithm) {
|
||||
muAllContentEncryptionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinContentEncryptionAlgorithm[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allContentEncryptionAlgorithm, alg.String())
|
||||
}
|
||||
muAllContentEncryptionAlgorithm.Unlock()
|
||||
rebuildContentEncryptionAlgorithm()
|
||||
}
|
||||
|
||||
func rebuildContentEncryptionAlgorithm() {
|
||||
list := make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithm))
|
||||
muAllContentEncryptionAlgorithm.RLock()
|
||||
for _, v := range allContentEncryptionAlgorithm {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllContentEncryptionAlgorithm.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListContentEncryptionAlgorithm.Lock()
|
||||
listContentEncryptionAlgorithm = list
|
||||
muListContentEncryptionAlgorithm.Unlock()
|
||||
}
|
||||
|
||||
// ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm.
|
||||
func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm {
|
||||
muListContentEncryptionAlgorithm.RLock()
|
||||
defer muListContentEncryptionAlgorithm.RUnlock()
|
||||
return listContentEncryptionAlgorithm
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the ContentEncryptionAlgorithm object to a JSON string.
|
||||
func (s ContentEncryptionAlgorithm) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a ContentEncryptionAlgorithm object.
|
||||
func (s *ContentEncryptionAlgorithm) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal ContentEncryptionAlgorithm: %w`, err)
|
||||
}
|
||||
v, ok := LookupContentEncryptionAlgorithm(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown ContentEncryptionAlgorithm: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var muAllEllipticCurveAlgorithm sync.RWMutex
|
||||
var allEllipticCurveAlgorithm = map[string]EllipticCurveAlgorithm{}
|
||||
var muListEllipticCurveAlgorithm sync.RWMutex
|
||||
var listEllipticCurveAlgorithm []EllipticCurveAlgorithm
|
||||
var builtinEllipticCurveAlgorithm = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for EllipticCurveAlgorithm
|
||||
algorithms := make([]EllipticCurveAlgorithm, 7)
|
||||
algorithms[0] = NewEllipticCurveAlgorithm("Ed25519")
|
||||
algorithms[1] = NewEllipticCurveAlgorithm("Ed448")
|
||||
algorithms[2] = NewEllipticCurveAlgorithm("P-256")
|
||||
algorithms[3] = NewEllipticCurveAlgorithm("P-384")
|
||||
algorithms[4] = NewEllipticCurveAlgorithm("P-521")
|
||||
algorithms[5] = NewEllipticCurveAlgorithm("X25519")
|
||||
algorithms[6] = NewEllipticCurveAlgorithm("X448")
|
||||
|
||||
RegisterEllipticCurveAlgorithm(algorithms...)
|
||||
}
|
||||
|
||||
// Ed25519 returns an object representing Ed25519 algorithm for EdDSA operations.
|
||||
func Ed25519() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("Ed25519")
|
||||
}
|
||||
|
||||
// Ed448 returns an object representing Ed448 algorithm for EdDSA operations.
|
||||
func Ed448() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("Ed448")
|
||||
}
|
||||
|
||||
var invalidEllipticCurve = NewEllipticCurveAlgorithm("P-invalid")
|
||||
|
||||
// InvalidEllipticCurve returns an object representing an invalid elliptic curve.
|
||||
func InvalidEllipticCurve() EllipticCurveAlgorithm {
|
||||
return invalidEllipticCurve
|
||||
}
|
||||
|
||||
// P256 returns an object representing P-256 algorithm for ECDSA operations.
|
||||
func P256() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("P-256")
|
||||
}
|
||||
|
||||
// P384 returns an object representing P-384 algorithm for ECDSA operations.
|
||||
func P384() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("P-384")
|
||||
}
|
||||
|
||||
// P521 returns an object representing P-521 algorithm for ECDSA operations.
|
||||
func P521() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("P-521")
|
||||
}
|
||||
|
||||
// X25519 returns an object representing X25519 algorithm for ECDH operations.
|
||||
func X25519() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("X25519")
|
||||
}
|
||||
|
||||
// X448 returns an object representing X448 algorithm for ECDH operations.
|
||||
func X448() EllipticCurveAlgorithm {
|
||||
return lookupBuiltinEllipticCurveAlgorithm("X448")
|
||||
}
|
||||
|
||||
func lookupBuiltinEllipticCurveAlgorithm(name string) EllipticCurveAlgorithm {
|
||||
muAllEllipticCurveAlgorithm.RLock()
|
||||
v, ok := allEllipticCurveAlgorithm[name]
|
||||
muAllEllipticCurveAlgorithm.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: EllipticCurveAlgorithm %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// EllipticCurveAlgorithm represents the algorithms used for EC keys
|
||||
type EllipticCurveAlgorithm struct {
|
||||
name string
|
||||
deprecated bool
|
||||
}
|
||||
|
||||
func (s EllipticCurveAlgorithm) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the EllipticCurveAlgorithm object is deprecated.
|
||||
func (s EllipticCurveAlgorithm) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// EmptyEllipticCurveAlgorithm returns an empty EllipticCurveAlgorithm object, used as a zero value.
|
||||
func EmptyEllipticCurveAlgorithm() EllipticCurveAlgorithm {
|
||||
return EllipticCurveAlgorithm{}
|
||||
}
|
||||
|
||||
// NewEllipticCurveAlgorithm creates a new EllipticCurveAlgorithm object with the given name.
|
||||
func NewEllipticCurveAlgorithm(name string, options ...NewAlgorithmOption) EllipticCurveAlgorithm {
|
||||
var deprecated bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewEllipticCurveAlgorithm: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return EllipticCurveAlgorithm{name: name, deprecated: deprecated}
|
||||
}
|
||||
|
||||
// LookupEllipticCurveAlgorithm returns the EllipticCurveAlgorithm object for the given name.
|
||||
func LookupEllipticCurveAlgorithm(name string) (EllipticCurveAlgorithm, bool) {
|
||||
muAllEllipticCurveAlgorithm.RLock()
|
||||
v, ok := allEllipticCurveAlgorithm[name]
|
||||
muAllEllipticCurveAlgorithm.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterEllipticCurveAlgorithm registers a new EllipticCurveAlgorithm. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterEllipticCurveAlgorithm(algorithms ...EllipticCurveAlgorithm) {
|
||||
muAllEllipticCurveAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allEllipticCurveAlgorithm[alg.String()] = alg
|
||||
}
|
||||
muAllEllipticCurveAlgorithm.Unlock()
|
||||
rebuildEllipticCurveAlgorithm()
|
||||
}
|
||||
|
||||
// UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterEllipticCurveAlgorithm(algorithms ...EllipticCurveAlgorithm) {
|
||||
muAllEllipticCurveAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinEllipticCurveAlgorithm[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allEllipticCurveAlgorithm, alg.String())
|
||||
}
|
||||
muAllEllipticCurveAlgorithm.Unlock()
|
||||
rebuildEllipticCurveAlgorithm()
|
||||
}
|
||||
|
||||
func rebuildEllipticCurveAlgorithm() {
|
||||
list := make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithm))
|
||||
muAllEllipticCurveAlgorithm.RLock()
|
||||
for _, v := range allEllipticCurveAlgorithm {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllEllipticCurveAlgorithm.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListEllipticCurveAlgorithm.Lock()
|
||||
listEllipticCurveAlgorithm = list
|
||||
muListEllipticCurveAlgorithm.Unlock()
|
||||
}
|
||||
|
||||
// EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm.
|
||||
func EllipticCurveAlgorithms() []EllipticCurveAlgorithm {
|
||||
muListEllipticCurveAlgorithm.RLock()
|
||||
defer muListEllipticCurveAlgorithm.RUnlock()
|
||||
return listEllipticCurveAlgorithm
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the EllipticCurveAlgorithm object to a JSON string.
|
||||
func (s EllipticCurveAlgorithm) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a EllipticCurveAlgorithm object.
|
||||
func (s *EllipticCurveAlgorithm) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal EllipticCurveAlgorithm: %w`, err)
|
||||
}
|
||||
v, ok := LookupEllipticCurveAlgorithm(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown EllipticCurveAlgorithm: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
//go:generate ../tools/cmd/genjwa.sh
|
||||
|
||||
// Package jwa defines the various algorithm described in https://tools.ietf.org/html/rfc7518
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// KeyAlgorithm is a workaround for jwk.Key being able to contain different
|
||||
// types of algorithms in its `alg` field.
|
||||
//
|
||||
// Previously the storage for the `alg` field was represented as a string,
|
||||
// but this caused some users to wonder why the field was not typed appropriately
|
||||
// like other fields.
|
||||
//
|
||||
// Ideally we would like to keep track of Signature Algorithms and
|
||||
// Key Encryption Algorithms separately, and force the APIs to
|
||||
// type-check at compile time, but this allows users to pass a value from a
|
||||
// jwk.Key directly
|
||||
type KeyAlgorithm interface {
|
||||
String() string
|
||||
IsDeprecated() bool
|
||||
}
|
||||
|
||||
var errInvalidKeyAlgorithm = errors.New(`invalid key algorithm`)
|
||||
|
||||
func ErrInvalidKeyAlgorithm() error {
|
||||
return errInvalidKeyAlgorithm
|
||||
}
|
||||
|
||||
// KeyAlgorithmFrom takes either a string, `jwa.SignatureAlgorithm`,
|
||||
// `jwa.KeyEncryptionAlgorithm`, or `jwa.ContentEncryptionAlgorithm`.
|
||||
// and returns a `jwa.KeyAlgorithm`.
|
||||
//
|
||||
// If the value cannot be handled, it returns an `jwa.InvalidKeyAlgorithm`
|
||||
// object instead of returning an error. This design choice was made to allow
|
||||
// users to directly pass the return value to functions such as `jws.Sign()`
|
||||
func KeyAlgorithmFrom(v any) (KeyAlgorithm, error) {
|
||||
switch v := v.(type) {
|
||||
case SignatureAlgorithm:
|
||||
return v, nil
|
||||
case KeyEncryptionAlgorithm:
|
||||
return v, nil
|
||||
case ContentEncryptionAlgorithm:
|
||||
return v, nil
|
||||
case string:
|
||||
salg, ok := LookupSignatureAlgorithm(v)
|
||||
if ok {
|
||||
return salg, nil
|
||||
}
|
||||
|
||||
kalg, ok := LookupKeyEncryptionAlgorithm(v)
|
||||
if ok {
|
||||
return kalg, nil
|
||||
}
|
||||
|
||||
calg, ok := LookupContentEncryptionAlgorithm(v)
|
||||
if ok {
|
||||
return calg, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`invalid key value: %q: %w`, v, errInvalidKeyAlgorithm)
|
||||
default:
|
||||
return nil, fmt.Errorf(`invalid key type: %T: %w`, v, errInvalidKeyAlgorithm)
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
var muAllKeyEncryptionAlgorithm sync.RWMutex
|
||||
var allKeyEncryptionAlgorithm = map[string]KeyEncryptionAlgorithm{}
|
||||
var muListKeyEncryptionAlgorithm sync.RWMutex
|
||||
var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm
|
||||
var builtinKeyEncryptionAlgorithm = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for KeyEncryptionAlgorithm
|
||||
algorithms := make([]KeyEncryptionAlgorithm, 19)
|
||||
algorithms[0] = NewKeyEncryptionAlgorithm(tokens.A128GCMKW, WithIsSymmetric(true))
|
||||
algorithms[1] = NewKeyEncryptionAlgorithm(tokens.A128KW, WithIsSymmetric(true))
|
||||
algorithms[2] = NewKeyEncryptionAlgorithm(tokens.A192GCMKW, WithIsSymmetric(true))
|
||||
algorithms[3] = NewKeyEncryptionAlgorithm(tokens.A192KW, WithIsSymmetric(true))
|
||||
algorithms[4] = NewKeyEncryptionAlgorithm(tokens.A256GCMKW, WithIsSymmetric(true))
|
||||
algorithms[5] = NewKeyEncryptionAlgorithm(tokens.A256KW, WithIsSymmetric(true))
|
||||
algorithms[6] = NewKeyEncryptionAlgorithm(tokens.DIRECT, WithIsSymmetric(true))
|
||||
algorithms[7] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES)
|
||||
algorithms[8] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A128KW)
|
||||
algorithms[9] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A192KW)
|
||||
algorithms[10] = NewKeyEncryptionAlgorithm(tokens.ECDH_ES_A256KW)
|
||||
algorithms[11] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS256_A128KW, WithIsSymmetric(true))
|
||||
algorithms[12] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS384_A192KW, WithIsSymmetric(true))
|
||||
algorithms[13] = NewKeyEncryptionAlgorithm(tokens.PBES2_HS512_A256KW, WithIsSymmetric(true))
|
||||
algorithms[14] = NewKeyEncryptionAlgorithm(tokens.RSA1_5, WithDeprecated(true))
|
||||
algorithms[15] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP)
|
||||
algorithms[16] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_256)
|
||||
algorithms[17] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_384)
|
||||
algorithms[18] = NewKeyEncryptionAlgorithm(tokens.RSA_OAEP_512)
|
||||
|
||||
RegisterKeyEncryptionAlgorithm(algorithms...)
|
||||
}
|
||||
|
||||
// A128GCMKW returns an object representing AES-GCM key wrap (128) key encryption algorithm.
|
||||
func A128GCMKW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A128GCMKW)
|
||||
}
|
||||
|
||||
// A128KW returns an object representing AES key wrap (128) key encryption algorithm.
|
||||
func A128KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A128KW)
|
||||
}
|
||||
|
||||
// A192GCMKW returns an object representing AES-GCM key wrap (192) key encryption algorithm.
|
||||
func A192GCMKW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A192GCMKW)
|
||||
}
|
||||
|
||||
// A192KW returns an object representing AES key wrap (192) key encryption algorithm.
|
||||
func A192KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A192KW)
|
||||
}
|
||||
|
||||
// A256GCMKW returns an object representing AES-GCM key wrap (256) key encryption algorithm.
|
||||
func A256GCMKW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A256GCMKW)
|
||||
}
|
||||
|
||||
// A256KW returns an object representing AES key wrap (256) key encryption algorithm.
|
||||
func A256KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.A256KW)
|
||||
}
|
||||
|
||||
// DIRECT returns an object representing Direct key encryption algorithm.
|
||||
func DIRECT() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.DIRECT)
|
||||
}
|
||||
|
||||
// ECDH_ES returns an object representing ECDH-ES key encryption algorithm.
|
||||
func ECDH_ES() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES)
|
||||
}
|
||||
|
||||
// ECDH_ES_A128KW returns an object representing ECDH-ES + AES key wrap (128) key encryption algorithm.
|
||||
func ECDH_ES_A128KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A128KW)
|
||||
}
|
||||
|
||||
// ECDH_ES_A192KW returns an object representing ECDH-ES + AES key wrap (192) key encryption algorithm.
|
||||
func ECDH_ES_A192KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A192KW)
|
||||
}
|
||||
|
||||
// ECDH_ES_A256KW returns an object representing ECDH-ES + AES key wrap (256) key encryption algorithm.
|
||||
func ECDH_ES_A256KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.ECDH_ES_A256KW)
|
||||
}
|
||||
|
||||
// PBES2_HS256_A128KW returns an object representing PBES2 + HMAC-SHA256 + AES key wrap (128) key encryption algorithm.
|
||||
func PBES2_HS256_A128KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS256_A128KW)
|
||||
}
|
||||
|
||||
// PBES2_HS384_A192KW returns an object representing PBES2 + HMAC-SHA384 + AES key wrap (192) key encryption algorithm.
|
||||
func PBES2_HS384_A192KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS384_A192KW)
|
||||
}
|
||||
|
||||
// PBES2_HS512_A256KW returns an object representing PBES2 + HMAC-SHA512 + AES key wrap (256) key encryption algorithm.
|
||||
func PBES2_HS512_A256KW() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.PBES2_HS512_A256KW)
|
||||
}
|
||||
|
||||
// RSA1_5 returns an object representing RSA-PKCS1v1.5 key encryption algorithm.
|
||||
func RSA1_5() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA1_5)
|
||||
}
|
||||
|
||||
// RSA_OAEP returns an object representing RSA-OAEP-SHA1 key encryption algorithm.
|
||||
func RSA_OAEP() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP)
|
||||
}
|
||||
|
||||
// RSA_OAEP_256 returns an object representing RSA-OAEP-SHA256 key encryption algorithm.
|
||||
func RSA_OAEP_256() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_256)
|
||||
}
|
||||
|
||||
// RSA_OAEP_384 returns an object representing RSA-OAEP-SHA384 key encryption algorithm.
|
||||
func RSA_OAEP_384() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_384)
|
||||
}
|
||||
|
||||
// RSA_OAEP_512 returns an object representing RSA-OAEP-SHA512 key encryption algorithm.
|
||||
func RSA_OAEP_512() KeyEncryptionAlgorithm {
|
||||
return lookupBuiltinKeyEncryptionAlgorithm(tokens.RSA_OAEP_512)
|
||||
}
|
||||
|
||||
func lookupBuiltinKeyEncryptionAlgorithm(name string) KeyEncryptionAlgorithm {
|
||||
muAllKeyEncryptionAlgorithm.RLock()
|
||||
v, ok := allKeyEncryptionAlgorithm[name]
|
||||
muAllKeyEncryptionAlgorithm.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: KeyEncryptionAlgorithm %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// KeyEncryptionAlgorithm represents the various encryption algorithms as described in https://tools.ietf.org/html/rfc7518#section-4.1
|
||||
type KeyEncryptionAlgorithm struct {
|
||||
name string
|
||||
deprecated bool
|
||||
isSymmetric bool
|
||||
}
|
||||
|
||||
func (s KeyEncryptionAlgorithm) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the KeyEncryptionAlgorithm object is deprecated.
|
||||
func (s KeyEncryptionAlgorithm) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// IsSymmetric returns true if the KeyEncryptionAlgorithm object is symmetric. Symmetric algorithms use the same key for both encryption and decryption.
|
||||
func (s KeyEncryptionAlgorithm) IsSymmetric() bool {
|
||||
return s.isSymmetric
|
||||
}
|
||||
|
||||
// EmptyKeyEncryptionAlgorithm returns an empty KeyEncryptionAlgorithm object, used as a zero value.
|
||||
func EmptyKeyEncryptionAlgorithm() KeyEncryptionAlgorithm {
|
||||
return KeyEncryptionAlgorithm{}
|
||||
}
|
||||
|
||||
// NewKeyEncryptionAlgorithm creates a new KeyEncryptionAlgorithm object with the given name.
|
||||
func NewKeyEncryptionAlgorithm(name string, options ...NewKeyEncryptionAlgorithmOption) KeyEncryptionAlgorithm {
|
||||
var deprecated bool
|
||||
var isSymmetric bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identIsSymmetric{}:
|
||||
if err := option.Value(&isSymmetric); err != nil {
|
||||
panic("jwa.NewKeyEncryptionAlgorithm: WithIsSymmetric option must be a boolean")
|
||||
}
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewKeyEncryptionAlgorithm: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return KeyEncryptionAlgorithm{name: name, deprecated: deprecated, isSymmetric: isSymmetric}
|
||||
}
|
||||
|
||||
// LookupKeyEncryptionAlgorithm returns the KeyEncryptionAlgorithm object for the given name.
|
||||
func LookupKeyEncryptionAlgorithm(name string) (KeyEncryptionAlgorithm, bool) {
|
||||
muAllKeyEncryptionAlgorithm.RLock()
|
||||
v, ok := allKeyEncryptionAlgorithm[name]
|
||||
muAllKeyEncryptionAlgorithm.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterKeyEncryptionAlgorithm registers a new KeyEncryptionAlgorithm. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterKeyEncryptionAlgorithm(algorithms ...KeyEncryptionAlgorithm) {
|
||||
muAllKeyEncryptionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allKeyEncryptionAlgorithm[alg.String()] = alg
|
||||
}
|
||||
muAllKeyEncryptionAlgorithm.Unlock()
|
||||
rebuildKeyEncryptionAlgorithm()
|
||||
}
|
||||
|
||||
// UnregisterKeyEncryptionAlgorithm unregisters a KeyEncryptionAlgorithm from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterKeyEncryptionAlgorithm(algorithms ...KeyEncryptionAlgorithm) {
|
||||
muAllKeyEncryptionAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinKeyEncryptionAlgorithm[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allKeyEncryptionAlgorithm, alg.String())
|
||||
}
|
||||
muAllKeyEncryptionAlgorithm.Unlock()
|
||||
rebuildKeyEncryptionAlgorithm()
|
||||
}
|
||||
|
||||
func rebuildKeyEncryptionAlgorithm() {
|
||||
list := make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithm))
|
||||
muAllKeyEncryptionAlgorithm.RLock()
|
||||
for _, v := range allKeyEncryptionAlgorithm {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllKeyEncryptionAlgorithm.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListKeyEncryptionAlgorithm.Lock()
|
||||
listKeyEncryptionAlgorithm = list
|
||||
muListKeyEncryptionAlgorithm.Unlock()
|
||||
}
|
||||
|
||||
// KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm.
|
||||
func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm {
|
||||
muListKeyEncryptionAlgorithm.RLock()
|
||||
defer muListKeyEncryptionAlgorithm.RUnlock()
|
||||
return listKeyEncryptionAlgorithm
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the KeyEncryptionAlgorithm object to a JSON string.
|
||||
func (s KeyEncryptionAlgorithm) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a KeyEncryptionAlgorithm object.
|
||||
func (s *KeyEncryptionAlgorithm) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal KeyEncryptionAlgorithm: %w`, err)
|
||||
}
|
||||
v, ok := LookupKeyEncryptionAlgorithm(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown KeyEncryptionAlgorithm: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var muAllKeyType sync.RWMutex
|
||||
var allKeyType = map[string]KeyType{}
|
||||
var muListKeyType sync.RWMutex
|
||||
var listKeyType []KeyType
|
||||
var builtinKeyType = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for KeyType
|
||||
algorithms := make([]KeyType, 4)
|
||||
algorithms[0] = NewKeyType("EC")
|
||||
algorithms[1] = NewKeyType("OKP")
|
||||
algorithms[2] = NewKeyType("oct")
|
||||
algorithms[3] = NewKeyType("RSA")
|
||||
|
||||
RegisterKeyType(algorithms...)
|
||||
}
|
||||
|
||||
// EC returns an object representing EC. Elliptic Curve
|
||||
func EC() KeyType {
|
||||
return lookupBuiltinKeyType("EC")
|
||||
}
|
||||
|
||||
var invalidKeyType = NewKeyType("")
|
||||
|
||||
// InvalidKeyType returns an object representing invalid key type. Invalid KeyType
|
||||
func InvalidKeyType() KeyType {
|
||||
return invalidKeyType
|
||||
}
|
||||
|
||||
// OKP returns an object representing OKP. Octet string key pairs
|
||||
func OKP() KeyType {
|
||||
return lookupBuiltinKeyType("OKP")
|
||||
}
|
||||
|
||||
// OctetSeq returns an object representing oct. Octet sequence (used to represent symmetric keys)
|
||||
func OctetSeq() KeyType {
|
||||
return lookupBuiltinKeyType("oct")
|
||||
}
|
||||
|
||||
// RSA returns an object representing RSA. RSA
|
||||
func RSA() KeyType {
|
||||
return lookupBuiltinKeyType("RSA")
|
||||
}
|
||||
|
||||
func lookupBuiltinKeyType(name string) KeyType {
|
||||
muAllKeyType.RLock()
|
||||
v, ok := allKeyType[name]
|
||||
muAllKeyType.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: KeyType %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// KeyType represents the key type ("kty") that are supported
|
||||
type KeyType struct {
|
||||
name string
|
||||
deprecated bool
|
||||
}
|
||||
|
||||
func (s KeyType) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the KeyType object is deprecated.
|
||||
func (s KeyType) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// EmptyKeyType returns an empty KeyType object, used as a zero value.
|
||||
func EmptyKeyType() KeyType {
|
||||
return KeyType{}
|
||||
}
|
||||
|
||||
// NewKeyType creates a new KeyType object with the given name.
|
||||
func NewKeyType(name string, options ...NewAlgorithmOption) KeyType {
|
||||
var deprecated bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewKeyType: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return KeyType{name: name, deprecated: deprecated}
|
||||
}
|
||||
|
||||
// LookupKeyType returns the KeyType object for the given name.
|
||||
func LookupKeyType(name string) (KeyType, bool) {
|
||||
muAllKeyType.RLock()
|
||||
v, ok := allKeyType[name]
|
||||
muAllKeyType.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterKeyType registers a new KeyType. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterKeyType(algorithms ...KeyType) {
|
||||
muAllKeyType.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allKeyType[alg.String()] = alg
|
||||
}
|
||||
muAllKeyType.Unlock()
|
||||
rebuildKeyType()
|
||||
}
|
||||
|
||||
// UnregisterKeyType unregisters a KeyType from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterKeyType(algorithms ...KeyType) {
|
||||
muAllKeyType.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinKeyType[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allKeyType, alg.String())
|
||||
}
|
||||
muAllKeyType.Unlock()
|
||||
rebuildKeyType()
|
||||
}
|
||||
|
||||
func rebuildKeyType() {
|
||||
list := make([]KeyType, 0, len(allKeyType))
|
||||
muAllKeyType.RLock()
|
||||
for _, v := range allKeyType {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllKeyType.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListKeyType.Lock()
|
||||
listKeyType = list
|
||||
muListKeyType.Unlock()
|
||||
}
|
||||
|
||||
// KeyTypes returns a list of all available values for KeyType.
|
||||
func KeyTypes() []KeyType {
|
||||
muListKeyType.RLock()
|
||||
defer muListKeyType.RUnlock()
|
||||
return listKeyType
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the KeyType object to a JSON string.
|
||||
func (s KeyType) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a KeyType object.
|
||||
func (s *KeyType) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal KeyType: %w`, err)
|
||||
}
|
||||
v, ok := LookupKeyType(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown KeyType: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package_name: jwa
|
||||
output: jwa/options_gen.go
|
||||
interfaces:
|
||||
- name: NewAlgorithmOption
|
||||
methods:
|
||||
- newSignatureAlgorithmOption
|
||||
- newKeyEncryptionAlgorithmOption
|
||||
- newSignatureKeyEncryptionAlgorithmOption
|
||||
comment: |
|
||||
NewAlgorithmOption represents an option that can be passed to any of the constructor functions
|
||||
- name: NewSignatureAlgorithmOption
|
||||
methods:
|
||||
- newSignatureAlgorithmOption
|
||||
comment: |
|
||||
NewSignatureAlgorithmOption represents an option that can be passed to the NewSignatureAlgorithm
|
||||
- name: NewKeyEncryptionAlgorithmOption
|
||||
methods:
|
||||
- newKeyEncryptionAlgorithmOption
|
||||
comment: |
|
||||
NewKeyEncryptionAlgorithmOption represents an option that can be passed to the NewKeyEncryptionAlgorithm
|
||||
- name: NewSignatureKeyEncryptionAlgorithmOption
|
||||
comment: |
|
||||
NewSignatureKeyEncryptionAlgorithmOption represents an option that can be passed to both
|
||||
NewSignatureAlgorithm and NewKeyEncryptionAlgorithm
|
||||
methods:
|
||||
- newSignatureAlgorithmOption
|
||||
- newKeyEncryptionAlgorithmOption
|
||||
options:
|
||||
- ident: IsSymmetric
|
||||
interface: NewSignatureKeyEncryptionAlgorithmOption
|
||||
argument_type: bool
|
||||
comment: |
|
||||
IsSymmetric specifies that the algorithm is symmetric
|
||||
- ident: Deprecated
|
||||
interface: NewAlgorithmOption
|
||||
argument_type: bool
|
||||
comment: |
|
||||
WithDeprecated specifies that the algorithm is deprecated. In order to
|
||||
un-deprecate an algorithm, you will have to create a new algorithm
|
||||
with the same values but with the Deprecated option set to false, and
|
||||
then call RegisterXXXXAlgorithm with the new algorithm.
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/option/v2"
|
||||
)
|
||||
|
||||
type Option = option.Interface
|
||||
|
||||
// NewAlgorithmOption represents an option that can be passed to any of the constructor functions
|
||||
type NewAlgorithmOption interface {
|
||||
Option
|
||||
newSignatureAlgorithmOption()
|
||||
newKeyEncryptionAlgorithmOption()
|
||||
newSignatureKeyEncryptionAlgorithmOption()
|
||||
}
|
||||
|
||||
type newAlgorithmOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*newAlgorithmOption) newSignatureAlgorithmOption() {}
|
||||
|
||||
func (*newAlgorithmOption) newKeyEncryptionAlgorithmOption() {}
|
||||
|
||||
func (*newAlgorithmOption) newSignatureKeyEncryptionAlgorithmOption() {}
|
||||
|
||||
// NewKeyEncryptionAlgorithmOption represents an option that can be passed to the NewKeyEncryptionAlgorithm
|
||||
type NewKeyEncryptionAlgorithmOption interface {
|
||||
Option
|
||||
newKeyEncryptionAlgorithmOption()
|
||||
}
|
||||
|
||||
type newKeyEncryptionAlgorithmOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*newKeyEncryptionAlgorithmOption) newKeyEncryptionAlgorithmOption() {}
|
||||
|
||||
// NewSignatureAlgorithmOption represents an option that can be passed to the NewSignatureAlgorithm
|
||||
type NewSignatureAlgorithmOption interface {
|
||||
Option
|
||||
newSignatureAlgorithmOption()
|
||||
}
|
||||
|
||||
type newSignatureAlgorithmOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*newSignatureAlgorithmOption) newSignatureAlgorithmOption() {}
|
||||
|
||||
// NewSignatureKeyEncryptionAlgorithmOption represents an option that can be passed to both
|
||||
// NewSignatureAlgorithm and NewKeyEncryptionAlgorithm
|
||||
type NewSignatureKeyEncryptionAlgorithmOption interface {
|
||||
Option
|
||||
newSignatureAlgorithmOption()
|
||||
newKeyEncryptionAlgorithmOption()
|
||||
}
|
||||
|
||||
type newSignatureKeyEncryptionAlgorithmOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*newSignatureKeyEncryptionAlgorithmOption) newSignatureAlgorithmOption() {}
|
||||
|
||||
func (*newSignatureKeyEncryptionAlgorithmOption) newKeyEncryptionAlgorithmOption() {}
|
||||
|
||||
type identDeprecated struct{}
|
||||
type identIsSymmetric struct{}
|
||||
|
||||
func (identDeprecated) String() string {
|
||||
return "WithDeprecated"
|
||||
}
|
||||
|
||||
func (identIsSymmetric) String() string {
|
||||
return "WithIsSymmetric"
|
||||
}
|
||||
|
||||
// WithDeprecated specifies that the algorithm is deprecated. In order to
|
||||
// un-deprecate an algorithm, you will have to create a new algorithm
|
||||
// with the same values but with the Deprecated option set to false, and
|
||||
// then call RegisterXXXXAlgorithm with the new algorithm.
|
||||
func WithDeprecated(v bool) NewAlgorithmOption {
|
||||
return &newAlgorithmOption{option.New(identDeprecated{}, v)}
|
||||
}
|
||||
|
||||
// IsSymmetric specifies that the algorithm is symmetric
|
||||
func WithIsSymmetric(v bool) NewSignatureKeyEncryptionAlgorithmOption {
|
||||
return &newSignatureKeyEncryptionAlgorithmOption{option.New(identIsSymmetric{}, v)}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//go:build jwx_es256k
|
||||
|
||||
package jwa
|
||||
|
||||
var secp256k1Algorithm = NewEllipticCurveAlgorithm("secp256k1")
|
||||
|
||||
// This constant is only available if compiled with jwx_es256k build tag
|
||||
func Secp256k1() EllipticCurveAlgorithm {
|
||||
return secp256k1Algorithm
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterEllipticCurveAlgorithm(secp256k1Algorithm)
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
// Code generated by tools/cmd/genjwa/main.go. DO NOT EDIT.
|
||||
|
||||
package jwa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var muAllSignatureAlgorithm sync.RWMutex
|
||||
var allSignatureAlgorithm = map[string]SignatureAlgorithm{}
|
||||
var muListSignatureAlgorithm sync.RWMutex
|
||||
var listSignatureAlgorithm []SignatureAlgorithm
|
||||
var builtinSignatureAlgorithm = map[string]struct{}{}
|
||||
|
||||
func init() {
|
||||
// builtin values for SignatureAlgorithm
|
||||
algorithms := make([]SignatureAlgorithm, 15)
|
||||
algorithms[0] = NewSignatureAlgorithm("ES256")
|
||||
algorithms[1] = NewSignatureAlgorithm("ES256K")
|
||||
algorithms[2] = NewSignatureAlgorithm("ES384")
|
||||
algorithms[3] = NewSignatureAlgorithm("ES512")
|
||||
algorithms[4] = NewSignatureAlgorithm("EdDSA")
|
||||
algorithms[5] = NewSignatureAlgorithm("HS256", WithIsSymmetric(true))
|
||||
algorithms[6] = NewSignatureAlgorithm("HS384", WithIsSymmetric(true))
|
||||
algorithms[7] = NewSignatureAlgorithm("HS512", WithIsSymmetric(true))
|
||||
algorithms[8] = NewSignatureAlgorithm("none")
|
||||
algorithms[9] = NewSignatureAlgorithm("PS256")
|
||||
algorithms[10] = NewSignatureAlgorithm("PS384")
|
||||
algorithms[11] = NewSignatureAlgorithm("PS512")
|
||||
algorithms[12] = NewSignatureAlgorithm("RS256")
|
||||
algorithms[13] = NewSignatureAlgorithm("RS384")
|
||||
algorithms[14] = NewSignatureAlgorithm("RS512")
|
||||
|
||||
RegisterSignatureAlgorithm(algorithms...)
|
||||
}
|
||||
|
||||
// ES256 returns an object representing ECDSA signature algorithm using P-256 curve and SHA-256.
|
||||
func ES256() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("ES256")
|
||||
}
|
||||
|
||||
// ES256K returns an object representing ECDSA signature algorithm using secp256k1 curve and SHA-256.
|
||||
func ES256K() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("ES256K")
|
||||
}
|
||||
|
||||
// ES384 returns an object representing ECDSA signature algorithm using P-384 curve and SHA-384.
|
||||
func ES384() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("ES384")
|
||||
}
|
||||
|
||||
// ES512 returns an object representing ECDSA signature algorithm using P-521 curve and SHA-512.
|
||||
func ES512() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("ES512")
|
||||
}
|
||||
|
||||
// EdDSA returns an object representing EdDSA signature algorithms.
|
||||
func EdDSA() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("EdDSA")
|
||||
}
|
||||
|
||||
// HS256 returns an object representing HMAC signature algorithm using SHA-256.
|
||||
func HS256() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("HS256")
|
||||
}
|
||||
|
||||
// HS384 returns an object representing HMAC signature algorithm using SHA-384.
|
||||
func HS384() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("HS384")
|
||||
}
|
||||
|
||||
// HS512 returns an object representing HMAC signature algorithm using SHA-512.
|
||||
func HS512() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("HS512")
|
||||
}
|
||||
|
||||
// NoSignature returns an object representing the lack of a signature algorithm. Using this value specifies that the content should not be signed, which you should avoid doing.
|
||||
func NoSignature() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("none")
|
||||
}
|
||||
|
||||
// PS256 returns an object representing RSASSA-PSS signature algorithm using SHA-256 and MGF1-SHA256.
|
||||
func PS256() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("PS256")
|
||||
}
|
||||
|
||||
// PS384 returns an object representing RSASSA-PSS signature algorithm using SHA-384 and MGF1-SHA384.
|
||||
func PS384() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("PS384")
|
||||
}
|
||||
|
||||
// PS512 returns an object representing RSASSA-PSS signature algorithm using SHA-512 and MGF1-SHA512.
|
||||
func PS512() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("PS512")
|
||||
}
|
||||
|
||||
// RS256 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-256.
|
||||
func RS256() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("RS256")
|
||||
}
|
||||
|
||||
// RS384 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-384.
|
||||
func RS384() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("RS384")
|
||||
}
|
||||
|
||||
// RS512 returns an object representing RSASSA-PKCS-v1.5 signature algorithm using SHA-512.
|
||||
func RS512() SignatureAlgorithm {
|
||||
return lookupBuiltinSignatureAlgorithm("RS512")
|
||||
}
|
||||
|
||||
func lookupBuiltinSignatureAlgorithm(name string) SignatureAlgorithm {
|
||||
muAllSignatureAlgorithm.RLock()
|
||||
v, ok := allSignatureAlgorithm[name]
|
||||
muAllSignatureAlgorithm.RUnlock()
|
||||
if !ok {
|
||||
panic(fmt.Sprintf(`jwa: SignatureAlgorithm %q not registered`, name))
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// SignatureAlgorithm represents the various signature algorithms as described in https://tools.ietf.org/html/rfc7518#section-3.1
|
||||
type SignatureAlgorithm struct {
|
||||
name string
|
||||
deprecated bool
|
||||
isSymmetric bool
|
||||
}
|
||||
|
||||
func (s SignatureAlgorithm) String() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// IsDeprecated returns true if the SignatureAlgorithm object is deprecated.
|
||||
func (s SignatureAlgorithm) IsDeprecated() bool {
|
||||
return s.deprecated
|
||||
}
|
||||
|
||||
// IsSymmetric returns true if the SignatureAlgorithm object is symmetric. Symmetric algorithms use the same key for both encryption and decryption.
|
||||
func (s SignatureAlgorithm) IsSymmetric() bool {
|
||||
return s.isSymmetric
|
||||
}
|
||||
|
||||
// EmptySignatureAlgorithm returns an empty SignatureAlgorithm object, used as a zero value.
|
||||
func EmptySignatureAlgorithm() SignatureAlgorithm {
|
||||
return SignatureAlgorithm{}
|
||||
}
|
||||
|
||||
// NewSignatureAlgorithm creates a new SignatureAlgorithm object with the given name.
|
||||
func NewSignatureAlgorithm(name string, options ...NewSignatureAlgorithmOption) SignatureAlgorithm {
|
||||
var deprecated bool
|
||||
var isSymmetric bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identIsSymmetric{}:
|
||||
if err := option.Value(&isSymmetric); err != nil {
|
||||
panic("jwa.NewSignatureAlgorithm: WithIsSymmetric option must be a boolean")
|
||||
}
|
||||
case identDeprecated{}:
|
||||
if err := option.Value(&deprecated); err != nil {
|
||||
panic("jwa.NewSignatureAlgorithm: WithDeprecated option must be a boolean")
|
||||
}
|
||||
}
|
||||
}
|
||||
return SignatureAlgorithm{name: name, deprecated: deprecated, isSymmetric: isSymmetric}
|
||||
}
|
||||
|
||||
// LookupSignatureAlgorithm returns the SignatureAlgorithm object for the given name.
|
||||
func LookupSignatureAlgorithm(name string) (SignatureAlgorithm, bool) {
|
||||
muAllSignatureAlgorithm.RLock()
|
||||
v, ok := allSignatureAlgorithm[name]
|
||||
muAllSignatureAlgorithm.RUnlock()
|
||||
return v, ok
|
||||
}
|
||||
|
||||
// RegisterSignatureAlgorithm registers a new SignatureAlgorithm. The signature value must be immutable
|
||||
// and safe to be used by multiple goroutines, as it is going to be shared with all other users of this library.
|
||||
func RegisterSignatureAlgorithm(algorithms ...SignatureAlgorithm) {
|
||||
muAllSignatureAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
allSignatureAlgorithm[alg.String()] = alg
|
||||
}
|
||||
muAllSignatureAlgorithm.Unlock()
|
||||
rebuildSignatureAlgorithm()
|
||||
}
|
||||
|
||||
// UnregisterSignatureAlgorithm unregisters a SignatureAlgorithm from its known database.
|
||||
// Non-existent entries, as well as built-in algorithms will silently be ignored.
|
||||
func UnregisterSignatureAlgorithm(algorithms ...SignatureAlgorithm) {
|
||||
muAllSignatureAlgorithm.Lock()
|
||||
for _, alg := range algorithms {
|
||||
if _, ok := builtinSignatureAlgorithm[alg.String()]; ok {
|
||||
continue
|
||||
}
|
||||
delete(allSignatureAlgorithm, alg.String())
|
||||
}
|
||||
muAllSignatureAlgorithm.Unlock()
|
||||
rebuildSignatureAlgorithm()
|
||||
}
|
||||
|
||||
func rebuildSignatureAlgorithm() {
|
||||
list := make([]SignatureAlgorithm, 0, len(allSignatureAlgorithm))
|
||||
muAllSignatureAlgorithm.RLock()
|
||||
for _, v := range allSignatureAlgorithm {
|
||||
list = append(list, v)
|
||||
}
|
||||
muAllSignatureAlgorithm.RUnlock()
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].String() < list[j].String()
|
||||
})
|
||||
muListSignatureAlgorithm.Lock()
|
||||
listSignatureAlgorithm = list
|
||||
muListSignatureAlgorithm.Unlock()
|
||||
}
|
||||
|
||||
// SignatureAlgorithms returns a list of all available values for SignatureAlgorithm.
|
||||
func SignatureAlgorithms() []SignatureAlgorithm {
|
||||
muListSignatureAlgorithm.RLock()
|
||||
defer muListSignatureAlgorithm.RUnlock()
|
||||
return listSignatureAlgorithm
|
||||
}
|
||||
|
||||
// MarshalJSON serializes the SignatureAlgorithm object to a JSON string.
|
||||
func (s SignatureAlgorithm) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.String())
|
||||
}
|
||||
|
||||
// UnmarshalJSON deserializes the JSON string to a SignatureAlgorithm object.
|
||||
func (s *SignatureAlgorithm) UnmarshalJSON(data []byte) error {
|
||||
var name string
|
||||
if err := json.Unmarshal(data, &name); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal SignatureAlgorithm: %w`, err)
|
||||
}
|
||||
v, ok := LookupSignatureAlgorithm(name)
|
||||
if !ok {
|
||||
return fmt.Errorf(`unknown SignatureAlgorithm: %q`, name)
|
||||
}
|
||||
*s = v
|
||||
return nil
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "jwe",
|
||||
srcs = [
|
||||
"compress.go",
|
||||
"decrypt.go",
|
||||
"encrypt.go",
|
||||
"errors.go",
|
||||
"filter.go",
|
||||
"headers.go",
|
||||
"headers_gen.go",
|
||||
"interface.go",
|
||||
"io.go",
|
||||
"jwe.go",
|
||||
"key_provider.go",
|
||||
"message.go",
|
||||
"options.go",
|
||||
"options_gen.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cert",
|
||||
"//internal/base64",
|
||||
"//transform",
|
||||
"//internal/json",
|
||||
"//internal/tokens",
|
||||
"//internal/keyconv",
|
||||
"//internal/pool",
|
||||
"//jwa",
|
||||
"//jwe/internal/aescbc",
|
||||
"//jwe/internal/cipher",
|
||||
"//jwe/internal/content_crypt",
|
||||
"//jwe/internal/keygen",
|
||||
"//jwe/jwebb",
|
||||
"//jwk",
|
||||
"@com_github_lestrrat_go_blackmagic//:blackmagic",
|
||||
"@com_github_lestrrat_go_option_v2//:option",
|
||||
"@org_golang_x_crypto//pbkdf2",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwe_test",
|
||||
srcs = [
|
||||
"filter_test.go",
|
||||
"gh402_test.go",
|
||||
"headers_test.go",
|
||||
"jwe_test.go",
|
||||
"message_test.go",
|
||||
"options_gen_test.go",
|
||||
"speed_test.go",
|
||||
],
|
||||
embed = [":jwe"],
|
||||
deps = [
|
||||
"//cert",
|
||||
"//internal/json",
|
||||
"//internal/jwxtest",
|
||||
"//jwa",
|
||||
"//jwk",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":jwe",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# JWE [](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwe)
|
||||
|
||||
Package jwe implements JWE as described in [RFC7516](https://tools.ietf.org/html/rfc7516)
|
||||
|
||||
* Encrypt and Decrypt arbitrary data
|
||||
* Content compression and decompression
|
||||
* Add arbitrary fields in the JWE header object
|
||||
|
||||
How-to style documentation can be found in the [docs directory](../docs).
|
||||
|
||||
Examples are located in the examples directory ([jwe_example_test.go](../examples/jwe_example_test.go))
|
||||
|
||||
Supported key encryption algorithm:
|
||||
|
||||
| Algorithm | Supported? | Constant in [jwa](../jwa) |
|
||||
|:-----------------------------------------|:-----------|:-------------------------|
|
||||
| RSA-PKCS1v1.5 | YES | jwa.RSA1_5 |
|
||||
| RSA-OAEP-SHA1 | YES | jwa.RSA_OAEP |
|
||||
| RSA-OAEP-SHA256 | YES | jwa.RSA_OAEP_256 |
|
||||
| AES key wrap (128) | YES | jwa.A128KW |
|
||||
| AES key wrap (192) | YES | jwa.A192KW |
|
||||
| AES key wrap (256) | YES | jwa.A256KW |
|
||||
| Direct encryption | YES (1) | jwa.DIRECT |
|
||||
| ECDH-ES | YES (1) | jwa.ECDH_ES |
|
||||
| ECDH-ES + AES key wrap (128) | YES | jwa.ECDH_ES_A128KW |
|
||||
| ECDH-ES + AES key wrap (192) | YES | jwa.ECDH_ES_A192KW |
|
||||
| ECDH-ES + AES key wrap (256) | YES | jwa.ECDH_ES_A256KW |
|
||||
| AES-GCM key wrap (128) | YES | jwa.A128GCMKW |
|
||||
| AES-GCM key wrap (192) | YES | jwa.A192GCMKW |
|
||||
| AES-GCM key wrap (256) | YES | jwa.A256GCMKW |
|
||||
| PBES2 + HMAC-SHA256 + AES key wrap (128) | YES | jwa.PBES2_HS256_A128KW |
|
||||
| PBES2 + HMAC-SHA384 + AES key wrap (192) | YES | jwa.PBES2_HS384_A192KW |
|
||||
| PBES2 + HMAC-SHA512 + AES key wrap (256) | YES | jwa.PBES2_HS512_A256KW |
|
||||
|
||||
* Note 1: Single-recipient only
|
||||
|
||||
Supported content encryption algorithm:
|
||||
|
||||
| Algorithm | Supported? | Constant in [jwa](../jwa) |
|
||||
|:----------------------------|:-----------|:--------------------------|
|
||||
| AES-CBC + HMAC-SHA256 (128) | YES | jwa.A128CBC_HS256 |
|
||||
| AES-CBC + HMAC-SHA384 (192) | YES | jwa.A192CBC_HS384 |
|
||||
| AES-CBC + HMAC-SHA512 (256) | YES | jwa.A256CBC_HS512 |
|
||||
| AES-GCM (128) | YES | jwa.A128GCM |
|
||||
| AES-GCM (192) | YES | jwa.A192GCM |
|
||||
| AES-GCM (256) | YES | jwa.A256GCM |
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
## Encrypt data
|
||||
|
||||
```go
|
||||
func ExampleEncrypt() {
|
||||
privkey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
log.Printf("failed to generate private key: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
payload := []byte("Lorem Ipsum")
|
||||
|
||||
encrypted, err := jwe.Encrypt(payload, jwe.WithKey(jwa.RSA1_5, &privkey.PublicKey), jwe.WithContentEncryption(jwa.A128CBC_HS256))
|
||||
if err != nil {
|
||||
log.Printf("failed to encrypt payload: %s", err)
|
||||
return
|
||||
}
|
||||
_ = encrypted
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
|
||||
## Decrypt data
|
||||
|
||||
```go
|
||||
func ExampleDecrypt() {
|
||||
privkey, encrypted, err := exampleGenPayload()
|
||||
if err != nil {
|
||||
log.Printf("failed to generate encrypted payload: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA1_5, privkey))
|
||||
if err != nil {
|
||||
log.Printf("failed to decrypt: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if string(decrypted) != "Lorem Ipsum" {
|
||||
log.Printf("WHAT?!")
|
||||
return
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
||||
)
|
||||
|
||||
func uncompress(src []byte, maxBufferSize int64) ([]byte, error) {
|
||||
var dst bytes.Buffer
|
||||
r := flate.NewReader(bytes.NewReader(src))
|
||||
defer r.Close()
|
||||
var buf [16384]byte
|
||||
var sofar int64
|
||||
for {
|
||||
n, readErr := r.Read(buf[:])
|
||||
sofar += int64(n)
|
||||
if sofar > maxBufferSize {
|
||||
return nil, fmt.Errorf(`compressed payload exceeds maximum allowed size`)
|
||||
}
|
||||
if readErr != nil {
|
||||
// if we have a read error, and it's not EOF, then we need to stop
|
||||
if readErr != io.EOF {
|
||||
return nil, fmt.Errorf(`failed to read inflated data: %w`, readErr)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := dst.Write(buf[:n]); err != nil {
|
||||
return nil, fmt.Errorf(`failed to write inflated data: %w`, err)
|
||||
}
|
||||
|
||||
if readErr != nil {
|
||||
// if it got here, then readErr == io.EOF, we're done
|
||||
return dst.Bytes(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func compress(plaintext []byte) ([]byte, error) {
|
||||
buf := pool.BytesBuffer().Get()
|
||||
defer pool.BytesBuffer().Put(buf)
|
||||
|
||||
w, _ := flate.NewWriter(buf, 1)
|
||||
in := plaintext
|
||||
for len(in) > 0 {
|
||||
n, err := w.Write(in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to write to compression writer: %w`, err)
|
||||
}
|
||||
in = in[n:]
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, fmt.Errorf(`failed to close compression writer: %w`, err)
|
||||
}
|
||||
|
||||
ret := make([]byte, buf.Len())
|
||||
copy(ret, buf.Bytes())
|
||||
return ret, nil
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/jwebb"
|
||||
)
|
||||
|
||||
// decrypter is responsible for taking various components to decrypt a message.
|
||||
// its operation is not concurrency safe. You must provide locking yourself
|
||||
//
|
||||
//nolint:govet
|
||||
type decrypter struct {
|
||||
aad []byte
|
||||
apu []byte
|
||||
apv []byte
|
||||
cek *[]byte
|
||||
computedAad []byte
|
||||
iv []byte
|
||||
keyiv []byte
|
||||
keysalt []byte
|
||||
keytag []byte
|
||||
tag []byte
|
||||
privkey any
|
||||
pubkey any
|
||||
ctalg jwa.ContentEncryptionAlgorithm
|
||||
keyalg jwa.KeyEncryptionAlgorithm
|
||||
cipher content_crypt.Cipher
|
||||
keycount int
|
||||
}
|
||||
|
||||
// newDecrypter Creates a new Decrypter instance. You must supply the
|
||||
// rest of parameters via their respective setter methods before
|
||||
// calling Decrypt().
|
||||
//
|
||||
// privkey must be a private key in its "raw" format (i.e. something like
|
||||
// *rsa.PrivateKey, instead of jwk.Key)
|
||||
//
|
||||
// You should consider this object immutable once you assign values to it.
|
||||
func newDecrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, privkey any) *decrypter {
|
||||
return &decrypter{
|
||||
ctalg: ctalg,
|
||||
keyalg: keyalg,
|
||||
privkey: privkey,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decrypter) AgreementPartyUInfo(apu []byte) *decrypter {
|
||||
d.apu = apu
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) AgreementPartyVInfo(apv []byte) *decrypter {
|
||||
d.apv = apv
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) AuthenticatedData(aad []byte) *decrypter {
|
||||
d.aad = aad
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) ComputedAuthenticatedData(aad []byte) *decrypter {
|
||||
d.computedAad = aad
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) ContentEncryptionAlgorithm(ctalg jwa.ContentEncryptionAlgorithm) *decrypter {
|
||||
d.ctalg = ctalg
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) InitializationVector(iv []byte) *decrypter {
|
||||
d.iv = iv
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) KeyCount(keycount int) *decrypter {
|
||||
d.keycount = keycount
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) KeyInitializationVector(keyiv []byte) *decrypter {
|
||||
d.keyiv = keyiv
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) KeySalt(keysalt []byte) *decrypter {
|
||||
d.keysalt = keysalt
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) KeyTag(keytag []byte) *decrypter {
|
||||
d.keytag = keytag
|
||||
return d
|
||||
}
|
||||
|
||||
// PublicKey sets the public key to be used in decoding EC based encryptions.
|
||||
// The key must be in its "raw" format (i.e. *ecdsa.PublicKey, instead of jwk.Key)
|
||||
func (d *decrypter) PublicKey(pubkey any) *decrypter {
|
||||
d.pubkey = pubkey
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) Tag(tag []byte) *decrypter {
|
||||
d.tag = tag
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) CEK(ptr *[]byte) *decrypter {
|
||||
d.cek = ptr
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) {
|
||||
if d.cipher == nil {
|
||||
cipher, err := jwebb.CreateContentCipher(d.ctalg.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.cipher = cipher
|
||||
}
|
||||
|
||||
return d.cipher, nil
|
||||
}
|
||||
|
||||
func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message) (plaintext []byte, err error) {
|
||||
cek, keyerr := d.DecryptKey(recipient, msg)
|
||||
if keyerr != nil {
|
||||
err = fmt.Errorf(`failed to decrypt key: %w`, keyerr)
|
||||
return
|
||||
}
|
||||
|
||||
cipher, ciphererr := d.ContentCipher()
|
||||
if ciphererr != nil {
|
||||
err = fmt.Errorf(`failed to fetch content crypt cipher: %w`, ciphererr)
|
||||
return
|
||||
}
|
||||
|
||||
computedAad := d.computedAad
|
||||
if d.aad != nil {
|
||||
computedAad = append(append(computedAad, tokens.Period), d.aad...)
|
||||
}
|
||||
|
||||
plaintext, err = cipher.Decrypt(cek, d.iv, ciphertext, d.tag, computedAad)
|
||||
if err != nil {
|
||||
err = fmt.Errorf(`failed to decrypt payload: %w`, err)
|
||||
return
|
||||
}
|
||||
|
||||
if d.cek != nil {
|
||||
*d.cek = cek
|
||||
}
|
||||
return plaintext, nil
|
||||
}
|
||||
|
||||
func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) {
|
||||
recipientKey := recipient.EncryptedKey()
|
||||
if kd, ok := d.privkey.(KeyDecrypter); ok {
|
||||
return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg)
|
||||
}
|
||||
|
||||
if jwebb.IsDirect(d.keyalg.String()) {
|
||||
cek, ok := d.privkey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey)
|
||||
}
|
||||
return jwebb.KeyDecryptDirect(recipientKey, recipientKey, d.keyalg.String(), cek)
|
||||
}
|
||||
|
||||
if jwebb.IsPBES2(d.keyalg.String()) {
|
||||
password, ok := d.privkey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("decrypt key: []byte is required as the password for %s (got %T)", d.keyalg, d.privkey)
|
||||
}
|
||||
salt := []byte(d.keyalg.String())
|
||||
salt = append(salt, byte(0))
|
||||
salt = append(salt, d.keysalt...)
|
||||
return jwebb.KeyDecryptPBES2(recipientKey, recipientKey, d.keyalg.String(), password, salt, d.keycount)
|
||||
}
|
||||
|
||||
if jwebb.IsAESGCMKW(d.keyalg.String()) {
|
||||
sharedkey, ok := d.privkey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("decrypt key: []byte is required as the key for %s (got %T)", d.keyalg, d.privkey)
|
||||
}
|
||||
return jwebb.KeyDecryptAESGCMKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey, d.keyiv, d.keytag)
|
||||
}
|
||||
|
||||
if jwebb.IsECDHES(d.keyalg.String()) {
|
||||
alg, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(d.keyalg.String(), d.ctalg.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err)
|
||||
}
|
||||
|
||||
if !keywrap {
|
||||
return jwebb.KeyDecryptECDHES(recipientKey, cek, alg, d.apu, d.apv, d.privkey, d.pubkey, keysize)
|
||||
}
|
||||
return jwebb.KeyDecryptECDHESKeyWrap(recipientKey, recipientKey, d.keyalg.String(), d.apu, d.apv, d.privkey, d.pubkey, keysize)
|
||||
}
|
||||
|
||||
if jwebb.IsRSA15(d.keyalg.String()) {
|
||||
cipher, err := d.ContentCipher()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to fetch content crypt cipher: %w`, err)
|
||||
}
|
||||
keysize := cipher.KeySize() / 2
|
||||
return jwebb.KeyDecryptRSA15(recipientKey, recipientKey, d.privkey, keysize)
|
||||
}
|
||||
|
||||
if jwebb.IsRSAOAEP(d.keyalg.String()) {
|
||||
return jwebb.KeyDecryptRSAOAEP(recipientKey, recipientKey, d.keyalg.String(), d.privkey)
|
||||
}
|
||||
|
||||
if jwebb.IsAESKW(d.keyalg.String()) {
|
||||
sharedkey, ok := d.privkey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("[]byte is required as the key to decrypt %s", d.keyalg.String())
|
||||
}
|
||||
return jwebb.KeyDecryptAESKW(recipientKey, recipientKey, d.keyalg.String(), sharedkey)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`unsupported algorithm for key decryption (%s)`, d.keyalg)
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/jwebb"
|
||||
)
|
||||
|
||||
// encrypter is responsible for taking various components to encrypt a key.
|
||||
// its operation is not concurrency safe. You must provide locking yourself
|
||||
//
|
||||
//nolint:govet
|
||||
type encrypter struct {
|
||||
apu []byte
|
||||
apv []byte
|
||||
ctalg jwa.ContentEncryptionAlgorithm
|
||||
keyalg jwa.KeyEncryptionAlgorithm
|
||||
pubkey any
|
||||
rawKey any
|
||||
cipher content_crypt.Cipher
|
||||
}
|
||||
|
||||
// newEncrypter creates a new Encrypter instance with all required parameters.
|
||||
// The content cipher is built internally during construction.
|
||||
//
|
||||
// pubkey must be a public key in its "raw" format (i.e. something like
|
||||
// *rsa.PublicKey, instead of jwk.Key)
|
||||
//
|
||||
// You should consider this object immutable once created.
|
||||
func newEncrypter(keyalg jwa.KeyEncryptionAlgorithm, ctalg jwa.ContentEncryptionAlgorithm, pubkey any, rawKey any, apu, apv []byte) (*encrypter, error) {
|
||||
cipher, err := jwebb.CreateContentCipher(ctalg.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create content cipher: %w`, err)
|
||||
}
|
||||
|
||||
return &encrypter{
|
||||
apu: apu,
|
||||
apv: apv,
|
||||
ctalg: ctalg,
|
||||
keyalg: keyalg,
|
||||
pubkey: pubkey,
|
||||
rawKey: rawKey,
|
||||
cipher: cipher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *encrypter) EncryptKey(cek []byte) (keygen.ByteSource, error) {
|
||||
if ke, ok := e.pubkey.(KeyEncrypter); ok {
|
||||
encrypted, err := ke.EncryptKey(cek)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return keygen.ByteKey(encrypted), nil
|
||||
}
|
||||
|
||||
if jwebb.IsDirect(e.keyalg.String()) {
|
||||
sharedkey, ok := e.rawKey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey)
|
||||
}
|
||||
return jwebb.KeyEncryptDirect(cek, e.keyalg.String(), sharedkey)
|
||||
}
|
||||
|
||||
if jwebb.IsPBES2(e.keyalg.String()) {
|
||||
password, ok := e.rawKey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("encrypt key: []byte is required as the password for %s (got %T)", e.keyalg, e.rawKey)
|
||||
}
|
||||
return jwebb.KeyEncryptPBES2(cek, e.keyalg.String(), password)
|
||||
}
|
||||
|
||||
if jwebb.IsAESGCMKW(e.keyalg.String()) {
|
||||
sharedkey, ok := e.rawKey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("encrypt key: []byte is required as the key for %s (got %T)", e.keyalg, e.rawKey)
|
||||
}
|
||||
return jwebb.KeyEncryptAESGCMKW(cek, e.keyalg.String(), sharedkey)
|
||||
}
|
||||
|
||||
if jwebb.IsECDHES(e.keyalg.String()) {
|
||||
_, keysize, keywrap, err := jwebb.KeyEncryptionECDHESKeySize(e.keyalg.String(), e.ctalg.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to determine ECDH-ES key size: %w`, err)
|
||||
}
|
||||
|
||||
// Use rawKey for ECDH-ES operations - it should contain the actual key material
|
||||
keyToUse := e.rawKey
|
||||
if keyToUse == nil {
|
||||
keyToUse = e.pubkey
|
||||
}
|
||||
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
// no op
|
||||
case ecdsa.PublicKey:
|
||||
keyToUse = &key
|
||||
case *ecdsa.PrivateKey:
|
||||
keyToUse = &key.PublicKey
|
||||
case ecdsa.PrivateKey:
|
||||
keyToUse = &key.PublicKey
|
||||
case *ecdh.PublicKey:
|
||||
// no op
|
||||
case ecdh.PublicKey:
|
||||
keyToUse = &key
|
||||
case ecdh.PrivateKey:
|
||||
keyToUse = key.PublicKey()
|
||||
case *ecdh.PrivateKey:
|
||||
keyToUse = key.PublicKey()
|
||||
}
|
||||
|
||||
// Determine key type and call appropriate function
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdh.PublicKey:
|
||||
if key.Curve() == ecdh.X25519() {
|
||||
if !keywrap {
|
||||
return jwebb.KeyEncryptECDHESX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptECDHESKeyWrapX25519(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
|
||||
var ecdsaKey *ecdsa.PublicKey
|
||||
if err := keyconv.ECDHToECDSA(&ecdsaKey, key); err != nil {
|
||||
return nil, fmt.Errorf(`encrypt: failed to convert ECDH public key to ECDSA: %w`, err)
|
||||
}
|
||||
keyToUse = ecdsaKey
|
||||
}
|
||||
|
||||
switch key := keyToUse.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
if !keywrap {
|
||||
return jwebb.KeyEncryptECDHESECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptECDHESKeyWrapECDSA(cek, e.keyalg.String(), e.apu, e.apv, key, keysize, e.ctalg.String())
|
||||
default:
|
||||
return nil, fmt.Errorf(`encrypt: unsupported key type for ECDH-ES: %T`, keyToUse)
|
||||
}
|
||||
}
|
||||
|
||||
if jwebb.IsRSA15(e.keyalg.String()) {
|
||||
keyToUse := e.rawKey
|
||||
if keyToUse == nil {
|
||||
keyToUse = e.pubkey
|
||||
}
|
||||
|
||||
// Handle rsa.PublicKey by value - convert to pointer
|
||||
if pk, ok := keyToUse.(rsa.PublicKey); ok {
|
||||
keyToUse = &pk
|
||||
}
|
||||
|
||||
var pubkey *rsa.PublicKey
|
||||
if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil {
|
||||
return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err)
|
||||
}
|
||||
|
||||
return jwebb.KeyEncryptRSA15(cek, e.keyalg.String(), pubkey)
|
||||
}
|
||||
|
||||
if jwebb.IsRSAOAEP(e.keyalg.String()) {
|
||||
keyToUse := e.rawKey
|
||||
if keyToUse == nil {
|
||||
keyToUse = e.pubkey
|
||||
}
|
||||
|
||||
// Handle rsa.PublicKey by value - convert to pointer
|
||||
if pk, ok := keyToUse.(rsa.PublicKey); ok {
|
||||
keyToUse = &pk
|
||||
}
|
||||
|
||||
var pubkey *rsa.PublicKey
|
||||
if err := keyconv.RSAPublicKey(&pubkey, keyToUse); err != nil {
|
||||
return nil, fmt.Errorf(`encrypt: failed to convert to RSA public key: %w`, err)
|
||||
}
|
||||
|
||||
return jwebb.KeyEncryptRSAOAEP(cek, e.keyalg.String(), pubkey)
|
||||
}
|
||||
|
||||
if jwebb.IsAESKW(e.keyalg.String()) {
|
||||
sharedkey, ok := e.rawKey.([]byte)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("[]byte is required as the key to encrypt %s", e.keyalg.String())
|
||||
}
|
||||
return jwebb.KeyEncryptAESKW(cek, e.keyalg.String(), sharedkey)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`unsupported algorithm for key encryption (%s)`, e.keyalg)
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package jwe
|
||||
|
||||
import "errors"
|
||||
|
||||
type encryptError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e encryptError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (encryptError) Is(err error) bool {
|
||||
_, ok := err.(encryptError)
|
||||
return ok
|
||||
}
|
||||
|
||||
var errDefaultEncryptError = encryptError{errors.New(`encrypt error`)}
|
||||
|
||||
// EncryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Encrypt`.
|
||||
func EncryptError() error {
|
||||
return errDefaultEncryptError
|
||||
}
|
||||
|
||||
type decryptError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e decryptError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (decryptError) Is(err error) bool {
|
||||
_, ok := err.(decryptError)
|
||||
return ok
|
||||
}
|
||||
|
||||
var errDefaultDecryptError = decryptError{errors.New(`decrypt error`)}
|
||||
|
||||
// DecryptError returns an error that can be passed to `errors.Is` to check if the error is an error returned by `jwe.Decrypt`.
|
||||
func DecryptError() error {
|
||||
return errDefaultDecryptError
|
||||
}
|
||||
|
||||
type recipientError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e recipientError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (recipientError) Is(err error) bool {
|
||||
_, ok := err.(recipientError)
|
||||
return ok
|
||||
}
|
||||
|
||||
var errDefaultRecipientError = recipientError{errors.New(`recipient error`)}
|
||||
|
||||
// RecipientError returns an error that can be passed to `errors.Is` to check if the error is
|
||||
// an error that occurred while attempting to decrypt a JWE message for a particular recipient.
|
||||
//
|
||||
// For example, if the JWE message failed to parse during `jwe.Decrypt`, it will be a
|
||||
// `jwe.DecryptError`, but NOT `jwe.RecipientError`. However, if the JWE message could not
|
||||
// be decrypted for any of the recipients, then it will be a `jwe.RecipientError`
|
||||
// (actually, it will be _multiple_ `jwe.RecipientError` errors, one for each recipient)
|
||||
func RecipientError() error {
|
||||
return errDefaultRecipientError
|
||||
}
|
||||
|
||||
type parseError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (e parseError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func (parseError) Is(err error) bool {
|
||||
_, ok := err.(parseError)
|
||||
return ok
|
||||
}
|
||||
|
||||
var errDefaultParseError = parseError{errors.New(`parse error`)}
|
||||
|
||||
// ParseError returns an error that can be passed to `errors.Is` to check if the error
|
||||
// is an error returned by `jwe.Parse` and related functions.
|
||||
func ParseError() error {
|
||||
return errDefaultParseError
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/transform"
|
||||
)
|
||||
|
||||
// HeaderFilter is an interface that allows users to filter JWE header fields.
|
||||
// It provides two methods: Filter and Reject; Filter returns a new header with only
|
||||
// the fields that match the filter criteria, while Reject returns a new header with
|
||||
// only the fields that DO NOT match the filter.
|
||||
//
|
||||
// EXPERIMENTAL: This API is experimental and its interface and behavior is
|
||||
// subject to change in future releases. This API is not subject to semver
|
||||
// compatibility guarantees.
|
||||
type HeaderFilter interface {
|
||||
Filter(header Headers) (Headers, error)
|
||||
Reject(header Headers) (Headers, error)
|
||||
}
|
||||
|
||||
// StandardHeadersFilter returns a HeaderFilter that filters out standard JWE header fields.
|
||||
//
|
||||
// You can use this filter to create headers that either only have standard fields
|
||||
// or only custom fields.
|
||||
//
|
||||
// If you need to configure the filter more precisely, consider
|
||||
// using the HeaderNameFilter directly.
|
||||
func StandardHeadersFilter() HeaderFilter {
|
||||
return stdHeadersFilter
|
||||
}
|
||||
|
||||
var stdHeadersFilter = NewHeaderNameFilter(stdHeaderNames...)
|
||||
|
||||
// NewHeaderNameFilter creates a new HeaderNameFilter with the specified field names.
|
||||
func NewHeaderNameFilter(names ...string) HeaderFilter {
|
||||
return transform.NewNameBasedFilter[Headers](names...)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
)
|
||||
|
||||
type isZeroer interface {
|
||||
isZero() bool
|
||||
}
|
||||
|
||||
func (h *stdHeaders) isZero() bool {
|
||||
return h.agreementPartyUInfo == nil &&
|
||||
h.agreementPartyVInfo == nil &&
|
||||
h.algorithm == nil &&
|
||||
h.compression == nil &&
|
||||
h.contentEncryption == nil &&
|
||||
h.contentType == nil &&
|
||||
h.critical == nil &&
|
||||
h.ephemeralPublicKey == nil &&
|
||||
h.jwk == nil &&
|
||||
h.jwkSetURL == nil &&
|
||||
h.keyID == nil &&
|
||||
h.typ == nil &&
|
||||
h.x509CertChain == nil &&
|
||||
h.x509CertThumbprint == nil &&
|
||||
h.x509CertThumbprintS256 == nil &&
|
||||
h.x509URL == nil &&
|
||||
len(h.privateParams) == 0
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Clone() (Headers, error) {
|
||||
dst := NewHeaders()
|
||||
if err := h.Copy(dst); err != nil {
|
||||
return nil, fmt.Errorf(`failed to copy header contents to new object: %w`, err)
|
||||
}
|
||||
return dst, nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Copy(dst Headers) error {
|
||||
for _, key := range h.Keys() {
|
||||
var v any
|
||||
if err := h.Get(key, &v); err != nil {
|
||||
return fmt.Errorf(`jwe.Headers: Copy: failed to get header %q: %w`, key, err)
|
||||
}
|
||||
|
||||
if err := dst.Set(key, v); err != nil {
|
||||
return fmt.Errorf(`jwe.Headers: Copy: failed to set header %q: %w`, key, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Merge(h2 Headers) (Headers, error) {
|
||||
h3 := NewHeaders()
|
||||
|
||||
if h != nil {
|
||||
if err := h.Copy(h3); err != nil {
|
||||
return nil, fmt.Errorf(`failed to copy headers from receiver: %w`, err)
|
||||
}
|
||||
}
|
||||
|
||||
if h2 != nil {
|
||||
if err := h2.Copy(h3); err != nil {
|
||||
return nil, fmt.Errorf(`failed to copy headers from argument: %w`, err)
|
||||
}
|
||||
}
|
||||
|
||||
return h3, nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Encode() ([]byte, error) {
|
||||
buf, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to marshal headers to JSON prior to encoding: %w`, err)
|
||||
}
|
||||
|
||||
return base64.Encode(buf), nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Decode(buf []byte) error {
|
||||
// base64 json string -> json object representation of header
|
||||
decoded, err := base64.Decode(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal base64 encoded buffer: %w`, err)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(decoded, h); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal buffer: %w`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+899
@@ -0,0 +1,899 @@
|
||||
// Code generated by tools/cmd/genjwe/main.go. DO NOT EDIT.
|
||||
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/lestrrat-go/blackmagic"
|
||||
"github.com/lestrrat-go/jwx/v3/cert"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
const (
|
||||
AgreementPartyUInfoKey = "apu"
|
||||
AgreementPartyVInfoKey = "apv"
|
||||
AlgorithmKey = "alg"
|
||||
CompressionKey = "zip"
|
||||
ContentEncryptionKey = "enc"
|
||||
ContentTypeKey = "cty"
|
||||
CriticalKey = "crit"
|
||||
EphemeralPublicKeyKey = "epk"
|
||||
JWKKey = "jwk"
|
||||
JWKSetURLKey = "jku"
|
||||
KeyIDKey = "kid"
|
||||
TypeKey = "typ"
|
||||
X509CertChainKey = "x5c"
|
||||
X509CertThumbprintKey = "x5t"
|
||||
X509CertThumbprintS256Key = "x5t#S256"
|
||||
X509URLKey = "x5u"
|
||||
)
|
||||
|
||||
// Headers describe a standard JWE Header set. It is part of the JWE message
|
||||
// and is used to represent both Protected and Unprotected headers,
|
||||
// which in turn can be found in each Recipient object.
|
||||
// If you are not sure how this works, it is strongly recommended that
|
||||
// you read RFC7516, especially the section
|
||||
// that describes the full JSON serialization format of JWE messages.
|
||||
//
|
||||
// In most cases, you likely want to use the protected headers, as this is the part of the encrypted content
|
||||
type Headers interface {
|
||||
AgreementPartyUInfo() ([]byte, bool)
|
||||
AgreementPartyVInfo() ([]byte, bool)
|
||||
Algorithm() (jwa.KeyEncryptionAlgorithm, bool)
|
||||
Compression() (jwa.CompressionAlgorithm, bool)
|
||||
ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool)
|
||||
ContentType() (string, bool)
|
||||
Critical() ([]string, bool)
|
||||
EphemeralPublicKey() (jwk.Key, bool)
|
||||
JWK() (jwk.Key, bool)
|
||||
JWKSetURL() (string, bool)
|
||||
KeyID() (string, bool)
|
||||
Type() (string, bool)
|
||||
X509CertChain() (*cert.Chain, bool)
|
||||
X509CertThumbprint() (string, bool)
|
||||
X509CertThumbprintS256() (string, bool)
|
||||
X509URL() (string, bool)
|
||||
|
||||
// Get is used to extract the value of any field, including non-standard fields, out of the header.
|
||||
//
|
||||
// The first argument is the name of the field. The second argument is a pointer
|
||||
// to a variable that will receive the value of the field. The method returns
|
||||
// an error if the field does not exist, or if the value cannot be assigned to
|
||||
// the destination variable. Note that a field is considered to "exist" even if
|
||||
// the value is empty-ish (e.g. 0, false, ""), as long as it is explicitly set.
|
||||
Get(string, any) error
|
||||
Set(string, any) error
|
||||
Remove(string) error
|
||||
// Has returns true if the specified header has a value, even if
|
||||
// the value is empty-ish (e.g. 0, false, "") as long as it has been
|
||||
// explicitly set.
|
||||
Has(string) bool
|
||||
Encode() ([]byte, error)
|
||||
Decode([]byte) error
|
||||
Clone() (Headers, error)
|
||||
Copy(Headers) error
|
||||
Merge(Headers) (Headers, error)
|
||||
|
||||
// Keys returns a list of the keys contained in this header.
|
||||
Keys() []string
|
||||
}
|
||||
|
||||
// stdHeaderNames is a list of all standard header names defined in the JWE specification.
|
||||
var stdHeaderNames = []string{AgreementPartyUInfoKey, AgreementPartyVInfoKey, AlgorithmKey, CompressionKey, ContentEncryptionKey, ContentTypeKey, CriticalKey, EphemeralPublicKeyKey, JWKKey, JWKSetURLKey, KeyIDKey, TypeKey, X509CertChainKey, X509CertThumbprintKey, X509CertThumbprintS256Key, X509URLKey}
|
||||
|
||||
type stdHeaders struct {
|
||||
agreementPartyUInfo []byte
|
||||
agreementPartyVInfo []byte
|
||||
algorithm *jwa.KeyEncryptionAlgorithm
|
||||
compression *jwa.CompressionAlgorithm
|
||||
contentEncryption *jwa.ContentEncryptionAlgorithm
|
||||
contentType *string
|
||||
critical []string
|
||||
ephemeralPublicKey jwk.Key
|
||||
jwk jwk.Key
|
||||
jwkSetURL *string
|
||||
keyID *string
|
||||
typ *string
|
||||
x509CertChain *cert.Chain
|
||||
x509CertThumbprint *string
|
||||
x509CertThumbprintS256 *string
|
||||
x509URL *string
|
||||
privateParams map[string]any
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHeaders() Headers {
|
||||
return &stdHeaders{
|
||||
mu: &sync.RWMutex{},
|
||||
privateParams: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *stdHeaders) AgreementPartyUInfo() ([]byte, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.agreementPartyUInfo, h.agreementPartyUInfo != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) AgreementPartyVInfo() ([]byte, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.agreementPartyVInfo, h.agreementPartyVInfo != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Algorithm() (jwa.KeyEncryptionAlgorithm, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.algorithm == nil {
|
||||
return jwa.EmptyKeyEncryptionAlgorithm(), false
|
||||
}
|
||||
return *(h.algorithm), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Compression() (jwa.CompressionAlgorithm, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.compression == nil {
|
||||
return jwa.NoCompress(), false
|
||||
}
|
||||
return *(h.compression), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) ContentEncryption() (jwa.ContentEncryptionAlgorithm, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.contentEncryption == nil {
|
||||
return jwa.EmptyContentEncryptionAlgorithm(), false
|
||||
}
|
||||
return *(h.contentEncryption), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) ContentType() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.contentType == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.contentType), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Critical() ([]string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.critical, h.critical != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) EphemeralPublicKey() (jwk.Key, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.ephemeralPublicKey, h.ephemeralPublicKey != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) JWK() (jwk.Key, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.jwk, h.jwk != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) JWKSetURL() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.jwkSetURL == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.jwkSetURL), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) KeyID() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.keyID == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.keyID), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Type() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.typ == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.typ), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) X509CertChain() (*cert.Chain, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.x509CertChain, h.x509CertChain != nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) X509CertThumbprint() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.x509CertThumbprint == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.x509CertThumbprint), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) X509CertThumbprintS256() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.x509CertThumbprintS256 == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.x509CertThumbprintS256), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) X509URL() (string, bool) {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
if h.x509URL == nil {
|
||||
return "", false
|
||||
}
|
||||
return *(h.x509URL), true
|
||||
}
|
||||
|
||||
func (h *stdHeaders) PrivateParams() map[string]any {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
return h.privateParams
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Has(name string) bool {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
switch name {
|
||||
case AgreementPartyUInfoKey:
|
||||
return h.agreementPartyUInfo != nil
|
||||
case AgreementPartyVInfoKey:
|
||||
return h.agreementPartyVInfo != nil
|
||||
case AlgorithmKey:
|
||||
return h.algorithm != nil
|
||||
case CompressionKey:
|
||||
return h.compression != nil
|
||||
case ContentEncryptionKey:
|
||||
return h.contentEncryption != nil
|
||||
case ContentTypeKey:
|
||||
return h.contentType != nil
|
||||
case CriticalKey:
|
||||
return h.critical != nil
|
||||
case EphemeralPublicKeyKey:
|
||||
return h.ephemeralPublicKey != nil
|
||||
case JWKKey:
|
||||
return h.jwk != nil
|
||||
case JWKSetURLKey:
|
||||
return h.jwkSetURL != nil
|
||||
case KeyIDKey:
|
||||
return h.keyID != nil
|
||||
case TypeKey:
|
||||
return h.typ != nil
|
||||
case X509CertChainKey:
|
||||
return h.x509CertChain != nil
|
||||
case X509CertThumbprintKey:
|
||||
return h.x509CertThumbprint != nil
|
||||
case X509CertThumbprintS256Key:
|
||||
return h.x509CertThumbprintS256 != nil
|
||||
case X509URLKey:
|
||||
return h.x509URL != nil
|
||||
default:
|
||||
_, ok := h.privateParams[name]
|
||||
return ok
|
||||
}
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Get(name string, dst any) error {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
switch name {
|
||||
case AgreementPartyUInfoKey:
|
||||
if h.agreementPartyUInfo == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyUInfo); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case AgreementPartyVInfoKey:
|
||||
if h.agreementPartyVInfo == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.agreementPartyVInfo); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case AlgorithmKey:
|
||||
if h.algorithm == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.algorithm)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case CompressionKey:
|
||||
if h.compression == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.compression)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case ContentEncryptionKey:
|
||||
if h.contentEncryption == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.contentEncryption)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case ContentTypeKey:
|
||||
if h.contentType == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.contentType)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case CriticalKey:
|
||||
if h.critical == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.critical); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case EphemeralPublicKeyKey:
|
||||
if h.ephemeralPublicKey == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.ephemeralPublicKey); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case JWKKey:
|
||||
if h.jwk == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.jwk); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case JWKSetURLKey:
|
||||
if h.jwkSetURL == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.jwkSetURL)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case KeyIDKey:
|
||||
if h.keyID == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.keyID)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case TypeKey:
|
||||
if h.typ == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.typ)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case X509CertChainKey:
|
||||
if h.x509CertChain == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, h.x509CertChain); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case X509CertThumbprintKey:
|
||||
if h.x509CertThumbprint == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprint)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case X509CertThumbprintS256Key:
|
||||
if h.x509CertThumbprintS256 == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.x509CertThumbprintS256)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
case X509URLKey:
|
||||
if h.x509URL == nil {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, *(h.x509URL)); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
default:
|
||||
v, ok := h.privateParams[name]
|
||||
if !ok {
|
||||
return fmt.Errorf(`field %q not found`, name)
|
||||
}
|
||||
if err := blackmagic.AssignIfCompatible(dst, v); err != nil {
|
||||
return fmt.Errorf(`failed to assign value for field %q: %w`, name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Set(name string, value any) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
return h.setNoLock(name, value)
|
||||
}
|
||||
|
||||
func (h *stdHeaders) setNoLock(name string, value any) error {
|
||||
switch name {
|
||||
case AgreementPartyUInfoKey:
|
||||
if v, ok := value.([]byte); ok {
|
||||
h.agreementPartyUInfo = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyUInfoKey, value)
|
||||
case AgreementPartyVInfoKey:
|
||||
if v, ok := value.([]byte); ok {
|
||||
h.agreementPartyVInfo = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, AgreementPartyVInfoKey, value)
|
||||
case AlgorithmKey:
|
||||
if v, ok := value.(jwa.KeyEncryptionAlgorithm); ok {
|
||||
h.algorithm = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, AlgorithmKey, value)
|
||||
case CompressionKey:
|
||||
if v, ok := value.(jwa.CompressionAlgorithm); ok {
|
||||
h.compression = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, CompressionKey, value)
|
||||
case ContentEncryptionKey:
|
||||
if v, ok := value.(jwa.ContentEncryptionAlgorithm); ok {
|
||||
if v == jwa.EmptyContentEncryptionAlgorithm() {
|
||||
return fmt.Errorf(`"enc" field cannot be an empty string`)
|
||||
}
|
||||
h.contentEncryption = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, ContentEncryptionKey, value)
|
||||
case ContentTypeKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.contentType = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, ContentTypeKey, value)
|
||||
case CriticalKey:
|
||||
if v, ok := value.([]string); ok {
|
||||
h.critical = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, CriticalKey, value)
|
||||
case EphemeralPublicKeyKey:
|
||||
if v, ok := value.(jwk.Key); ok {
|
||||
h.ephemeralPublicKey = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, EphemeralPublicKeyKey, value)
|
||||
case JWKKey:
|
||||
if v, ok := value.(jwk.Key); ok {
|
||||
h.jwk = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, JWKKey, value)
|
||||
case JWKSetURLKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.jwkSetURL = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, JWKSetURLKey, value)
|
||||
case KeyIDKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.keyID = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, KeyIDKey, value)
|
||||
case TypeKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.typ = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, TypeKey, value)
|
||||
case X509CertChainKey:
|
||||
if v, ok := value.(*cert.Chain); ok {
|
||||
h.x509CertChain = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, X509CertChainKey, value)
|
||||
case X509CertThumbprintKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.x509CertThumbprint = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintKey, value)
|
||||
case X509CertThumbprintS256Key:
|
||||
if v, ok := value.(string); ok {
|
||||
h.x509CertThumbprintS256 = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, X509CertThumbprintS256Key, value)
|
||||
case X509URLKey:
|
||||
if v, ok := value.(string); ok {
|
||||
h.x509URL = &v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf(`invalid value for %s key: %T`, X509URLKey, value)
|
||||
default:
|
||||
if h.privateParams == nil {
|
||||
h.privateParams = map[string]any{}
|
||||
}
|
||||
h.privateParams[name] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Remove(key string) error {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
switch key {
|
||||
case AgreementPartyUInfoKey:
|
||||
h.agreementPartyUInfo = nil
|
||||
case AgreementPartyVInfoKey:
|
||||
h.agreementPartyVInfo = nil
|
||||
case AlgorithmKey:
|
||||
h.algorithm = nil
|
||||
case CompressionKey:
|
||||
h.compression = nil
|
||||
case ContentEncryptionKey:
|
||||
h.contentEncryption = nil
|
||||
case ContentTypeKey:
|
||||
h.contentType = nil
|
||||
case CriticalKey:
|
||||
h.critical = nil
|
||||
case EphemeralPublicKeyKey:
|
||||
h.ephemeralPublicKey = nil
|
||||
case JWKKey:
|
||||
h.jwk = nil
|
||||
case JWKSetURLKey:
|
||||
h.jwkSetURL = nil
|
||||
case KeyIDKey:
|
||||
h.keyID = nil
|
||||
case TypeKey:
|
||||
h.typ = nil
|
||||
case X509CertChainKey:
|
||||
h.x509CertChain = nil
|
||||
case X509CertThumbprintKey:
|
||||
h.x509CertThumbprint = nil
|
||||
case X509CertThumbprintS256Key:
|
||||
h.x509CertThumbprintS256 = nil
|
||||
case X509URLKey:
|
||||
h.x509URL = nil
|
||||
default:
|
||||
delete(h.privateParams, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) UnmarshalJSON(buf []byte) error {
|
||||
h.agreementPartyUInfo = nil
|
||||
h.agreementPartyVInfo = nil
|
||||
h.algorithm = nil
|
||||
h.compression = nil
|
||||
h.contentEncryption = nil
|
||||
h.contentType = nil
|
||||
h.critical = nil
|
||||
h.ephemeralPublicKey = nil
|
||||
h.jwk = nil
|
||||
h.jwkSetURL = nil
|
||||
h.keyID = nil
|
||||
h.typ = nil
|
||||
h.x509CertChain = nil
|
||||
h.x509CertThumbprint = nil
|
||||
h.x509CertThumbprintS256 = nil
|
||||
h.x509URL = nil
|
||||
dec := json.NewDecoder(bytes.NewReader(buf))
|
||||
LOOP:
|
||||
for {
|
||||
tok, err := dec.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`error reading token: %w`, err)
|
||||
}
|
||||
switch tok := tok.(type) {
|
||||
case json.Delim:
|
||||
// Assuming we're doing everything correctly, we should ONLY
|
||||
// get either tokens.OpenCurlyBracket or tokens.CloseCurlyBracket here.
|
||||
if tok == tokens.CloseCurlyBracket { // End of object
|
||||
break LOOP
|
||||
} else if tok != tokens.OpenCurlyBracket {
|
||||
return fmt.Errorf(`expected '%c' but got '%c'`, tokens.OpenCurlyBracket, tok)
|
||||
}
|
||||
case string: // Objects can only have string keys
|
||||
switch tok {
|
||||
case AgreementPartyUInfoKey:
|
||||
if err := json.AssignNextBytesToken(&h.agreementPartyUInfo, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyUInfoKey, err)
|
||||
}
|
||||
case AgreementPartyVInfoKey:
|
||||
if err := json.AssignNextBytesToken(&h.agreementPartyVInfo, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, AgreementPartyVInfoKey, err)
|
||||
}
|
||||
case AlgorithmKey:
|
||||
var decoded jwa.KeyEncryptionAlgorithm
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, AlgorithmKey, err)
|
||||
}
|
||||
h.algorithm = &decoded
|
||||
case CompressionKey:
|
||||
var decoded jwa.CompressionAlgorithm
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, CompressionKey, err)
|
||||
}
|
||||
h.compression = &decoded
|
||||
case ContentEncryptionKey:
|
||||
var decoded jwa.ContentEncryptionAlgorithm
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentEncryptionKey, err)
|
||||
}
|
||||
h.contentEncryption = &decoded
|
||||
case ContentTypeKey:
|
||||
if err := json.AssignNextStringToken(&h.contentType, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, ContentTypeKey, err)
|
||||
}
|
||||
case CriticalKey:
|
||||
var decoded []string
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, CriticalKey, err)
|
||||
}
|
||||
h.critical = decoded
|
||||
case EphemeralPublicKeyKey:
|
||||
var buf json.RawMessage
|
||||
if err := dec.Decode(&buf); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s:%w`, EphemeralPublicKeyKey, err)
|
||||
}
|
||||
key, err := jwk.ParseKey(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to parse JWK for key %s: %w`, EphemeralPublicKeyKey, err)
|
||||
}
|
||||
h.ephemeralPublicKey = key
|
||||
case JWKKey:
|
||||
var buf json.RawMessage
|
||||
if err := dec.Decode(&buf); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s:%w`, JWKKey, err)
|
||||
}
|
||||
key, err := jwk.ParseKey(buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to parse JWK for key %s: %w`, JWKKey, err)
|
||||
}
|
||||
h.jwk = key
|
||||
case JWKSetURLKey:
|
||||
if err := json.AssignNextStringToken(&h.jwkSetURL, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, JWKSetURLKey, err)
|
||||
}
|
||||
case KeyIDKey:
|
||||
if err := json.AssignNextStringToken(&h.keyID, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, KeyIDKey, err)
|
||||
}
|
||||
case TypeKey:
|
||||
if err := json.AssignNextStringToken(&h.typ, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, TypeKey, err)
|
||||
}
|
||||
case X509CertChainKey:
|
||||
var decoded cert.Chain
|
||||
if err := dec.Decode(&decoded); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertChainKey, err)
|
||||
}
|
||||
h.x509CertChain = &decoded
|
||||
case X509CertThumbprintKey:
|
||||
if err := json.AssignNextStringToken(&h.x509CertThumbprint, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintKey, err)
|
||||
}
|
||||
case X509CertThumbprintS256Key:
|
||||
if err := json.AssignNextStringToken(&h.x509CertThumbprintS256, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, X509CertThumbprintS256Key, err)
|
||||
}
|
||||
case X509URLKey:
|
||||
if err := json.AssignNextStringToken(&h.x509URL, dec); err != nil {
|
||||
return fmt.Errorf(`failed to decode value for key %s: %w`, X509URLKey, err)
|
||||
}
|
||||
default:
|
||||
decoded, err := registry.Decode(dec, tok)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.setNoLock(tok, decoded)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(`invalid token %T`, tok)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) Keys() []string {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
keys := make([]string, 0, 16+len(h.privateParams))
|
||||
if h.agreementPartyUInfo != nil {
|
||||
keys = append(keys, AgreementPartyUInfoKey)
|
||||
}
|
||||
if h.agreementPartyVInfo != nil {
|
||||
keys = append(keys, AgreementPartyVInfoKey)
|
||||
}
|
||||
if h.algorithm != nil {
|
||||
keys = append(keys, AlgorithmKey)
|
||||
}
|
||||
if h.compression != nil {
|
||||
keys = append(keys, CompressionKey)
|
||||
}
|
||||
if h.contentEncryption != nil {
|
||||
keys = append(keys, ContentEncryptionKey)
|
||||
}
|
||||
if h.contentType != nil {
|
||||
keys = append(keys, ContentTypeKey)
|
||||
}
|
||||
if h.critical != nil {
|
||||
keys = append(keys, CriticalKey)
|
||||
}
|
||||
if h.ephemeralPublicKey != nil {
|
||||
keys = append(keys, EphemeralPublicKeyKey)
|
||||
}
|
||||
if h.jwk != nil {
|
||||
keys = append(keys, JWKKey)
|
||||
}
|
||||
if h.jwkSetURL != nil {
|
||||
keys = append(keys, JWKSetURLKey)
|
||||
}
|
||||
if h.keyID != nil {
|
||||
keys = append(keys, KeyIDKey)
|
||||
}
|
||||
if h.typ != nil {
|
||||
keys = append(keys, TypeKey)
|
||||
}
|
||||
if h.x509CertChain != nil {
|
||||
keys = append(keys, X509CertChainKey)
|
||||
}
|
||||
if h.x509CertThumbprint != nil {
|
||||
keys = append(keys, X509CertThumbprintKey)
|
||||
}
|
||||
if h.x509CertThumbprintS256 != nil {
|
||||
keys = append(keys, X509CertThumbprintS256Key)
|
||||
}
|
||||
if h.x509URL != nil {
|
||||
keys = append(keys, X509URLKey)
|
||||
}
|
||||
for k := range h.privateParams {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (h stdHeaders) MarshalJSON() ([]byte, error) {
|
||||
data := make(map[string]any)
|
||||
keys := make([]string, 0, 16+len(h.privateParams))
|
||||
h.mu.RLock()
|
||||
if h.agreementPartyUInfo != nil {
|
||||
data[AgreementPartyUInfoKey] = h.agreementPartyUInfo
|
||||
keys = append(keys, AgreementPartyUInfoKey)
|
||||
}
|
||||
if h.agreementPartyVInfo != nil {
|
||||
data[AgreementPartyVInfoKey] = h.agreementPartyVInfo
|
||||
keys = append(keys, AgreementPartyVInfoKey)
|
||||
}
|
||||
if h.algorithm != nil {
|
||||
data[AlgorithmKey] = *(h.algorithm)
|
||||
keys = append(keys, AlgorithmKey)
|
||||
}
|
||||
if h.compression != nil {
|
||||
data[CompressionKey] = *(h.compression)
|
||||
keys = append(keys, CompressionKey)
|
||||
}
|
||||
if h.contentEncryption != nil {
|
||||
data[ContentEncryptionKey] = *(h.contentEncryption)
|
||||
keys = append(keys, ContentEncryptionKey)
|
||||
}
|
||||
if h.contentType != nil {
|
||||
data[ContentTypeKey] = *(h.contentType)
|
||||
keys = append(keys, ContentTypeKey)
|
||||
}
|
||||
if h.critical != nil {
|
||||
data[CriticalKey] = h.critical
|
||||
keys = append(keys, CriticalKey)
|
||||
}
|
||||
if h.ephemeralPublicKey != nil {
|
||||
data[EphemeralPublicKeyKey] = h.ephemeralPublicKey
|
||||
keys = append(keys, EphemeralPublicKeyKey)
|
||||
}
|
||||
if h.jwk != nil {
|
||||
data[JWKKey] = h.jwk
|
||||
keys = append(keys, JWKKey)
|
||||
}
|
||||
if h.jwkSetURL != nil {
|
||||
data[JWKSetURLKey] = *(h.jwkSetURL)
|
||||
keys = append(keys, JWKSetURLKey)
|
||||
}
|
||||
if h.keyID != nil {
|
||||
data[KeyIDKey] = *(h.keyID)
|
||||
keys = append(keys, KeyIDKey)
|
||||
}
|
||||
if h.typ != nil {
|
||||
data[TypeKey] = *(h.typ)
|
||||
keys = append(keys, TypeKey)
|
||||
}
|
||||
if h.x509CertChain != nil {
|
||||
data[X509CertChainKey] = h.x509CertChain
|
||||
keys = append(keys, X509CertChainKey)
|
||||
}
|
||||
if h.x509CertThumbprint != nil {
|
||||
data[X509CertThumbprintKey] = *(h.x509CertThumbprint)
|
||||
keys = append(keys, X509CertThumbprintKey)
|
||||
}
|
||||
if h.x509CertThumbprintS256 != nil {
|
||||
data[X509CertThumbprintS256Key] = *(h.x509CertThumbprintS256)
|
||||
keys = append(keys, X509CertThumbprintS256Key)
|
||||
}
|
||||
if h.x509URL != nil {
|
||||
data[X509URLKey] = *(h.x509URL)
|
||||
keys = append(keys, X509URLKey)
|
||||
}
|
||||
for k, v := range h.privateParams {
|
||||
data[k] = v
|
||||
keys = append(keys, k)
|
||||
}
|
||||
h.mu.RUnlock()
|
||||
|
||||
sort.Strings(keys)
|
||||
buf := pool.BytesBuffer().Get()
|
||||
defer pool.BytesBuffer().Put(buf)
|
||||
enc := json.NewEncoder(buf)
|
||||
buf.WriteByte(tokens.OpenCurlyBracket)
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
buf.WriteRune(tokens.Comma)
|
||||
}
|
||||
buf.WriteRune(tokens.DoubleQuote)
|
||||
buf.WriteString(k)
|
||||
buf.WriteString(`":`)
|
||||
v := data[k]
|
||||
switch v := v.(type) {
|
||||
case []byte:
|
||||
buf.WriteRune(tokens.DoubleQuote)
|
||||
buf.WriteString(base64.EncodeToString(v))
|
||||
buf.WriteRune(tokens.DoubleQuote)
|
||||
default:
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode value for field %s`, k)
|
||||
}
|
||||
buf.Truncate(buf.Len() - 1)
|
||||
}
|
||||
}
|
||||
buf.WriteByte(tokens.CloseCurlyBracket)
|
||||
ret := make([]byte, buf.Len())
|
||||
copy(ret, buf.Bytes())
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (h *stdHeaders) clear() {
|
||||
h.mu.Lock()
|
||||
h.agreementPartyUInfo = nil
|
||||
h.agreementPartyVInfo = nil
|
||||
h.algorithm = nil
|
||||
h.compression = nil
|
||||
h.contentEncryption = nil
|
||||
h.contentType = nil
|
||||
h.critical = nil
|
||||
h.ephemeralPublicKey = nil
|
||||
h.jwk = nil
|
||||
h.jwkSetURL = nil
|
||||
h.keyID = nil
|
||||
h.typ = nil
|
||||
h.x509CertChain = nil
|
||||
h.x509CertThumbprint = nil
|
||||
h.x509CertThumbprintS256 = nil
|
||||
h.x509URL = nil
|
||||
h.privateParams = map[string]any{}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
)
|
||||
|
||||
// KeyEncrypter is an interface for object that can encrypt a
|
||||
// content encryption key.
|
||||
//
|
||||
// You can use this in place of a regular key (i.e. in jwe.WithKey())
|
||||
// to encrypt the content encryption key in a JWE message without
|
||||
// having to expose the secret key in memory, for example, when you
|
||||
// want to use hardware security modules (HSMs) to encrypt the key.
|
||||
//
|
||||
// This API is experimental and may change without notice, even
|
||||
// in minor releases.
|
||||
type KeyEncrypter interface {
|
||||
// Algorithm returns the algorithm used to encrypt the key.
|
||||
Algorithm() jwa.KeyEncryptionAlgorithm
|
||||
|
||||
// EncryptKey encrypts the given content encryption key.
|
||||
EncryptKey([]byte) ([]byte, error)
|
||||
}
|
||||
|
||||
// KeyIDer is an interface for things that can return a key ID.
|
||||
//
|
||||
// As of this writing, this is solely used to identify KeyEncrypter
|
||||
// objects that also carry a key ID on its own.
|
||||
type KeyIDer interface {
|
||||
KeyID() (string, bool)
|
||||
}
|
||||
|
||||
// KeyDecrypter is an interface for objects that can decrypt a content
|
||||
// encryption key.
|
||||
//
|
||||
// You can use this in place of a regular key (i.e. in jwe.WithKey())
|
||||
// to decrypt the encrypted key in a JWE message without having to
|
||||
// expose the secret key in memory, for example, when you want to use
|
||||
// hardware security modules (HSMs) to decrypt the key.
|
||||
//
|
||||
// This API is experimental and may change without notice, even
|
||||
// in minor releases.
|
||||
type KeyDecrypter interface {
|
||||
// Decrypt decrypts the encrypted key of a JWE message.
|
||||
//
|
||||
// Make sure you understand how JWE messages are structured.
|
||||
//
|
||||
// For example, while in most circumstances a JWE message will only have one recipient,
|
||||
// a JWE message may contain multiple recipients, each with their own
|
||||
// encrypted key. This method will be called for each recipient, instead of
|
||||
// just once for a message.
|
||||
//
|
||||
// Also, header values could be found in either protected/unprotected headers
|
||||
// of a JWE message, as well as in protected/unprotected headers for each recipient.
|
||||
// When checking a header value, you can decide to use either one, or both, but you
|
||||
// must be aware that there are multiple places to look for.
|
||||
DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error)
|
||||
}
|
||||
|
||||
// Recipient holds the encrypted key and hints to decrypt the key
|
||||
type Recipient interface {
|
||||
Headers() Headers
|
||||
EncryptedKey() []byte
|
||||
SetHeaders(Headers) error
|
||||
SetEncryptedKey([]byte) error
|
||||
}
|
||||
|
||||
type stdRecipient struct {
|
||||
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
|
||||
//
|
||||
// header
|
||||
// The "header" member MUST be present and contain the value JWE Per-
|
||||
// Recipient Unprotected Header when the JWE Per-Recipient
|
||||
// Unprotected Header value is non-empty; otherwise, it MUST be
|
||||
// absent. This value is represented as an unencoded JSON object,
|
||||
// rather than as a string. These Header Parameter values are not
|
||||
// integrity protected.
|
||||
//
|
||||
// At least one of the "header", "protected", and "unprotected" members
|
||||
// MUST be present so that "alg" and "enc" Header Parameter values are
|
||||
// conveyed for each recipient computation.
|
||||
//
|
||||
// JWX note: see Message.unprotectedHeaders
|
||||
headers Headers
|
||||
|
||||
// encrypted_key
|
||||
// The "encrypted_key" member MUST be present and contain the value
|
||||
// BASE64URL(JWE Encrypted Key) when the JWE Encrypted Key value is
|
||||
// non-empty; otherwise, it MUST be absent.
|
||||
encryptedKey []byte
|
||||
}
|
||||
|
||||
// Message contains the entire encrypted JWE message. You should not
|
||||
// expect to use Message for anything other than inspecting the
|
||||
// state of an encrypted message. This is because encryption is
|
||||
// highly context-sensitive, and once we parse the original payload
|
||||
// into an object, we may not always be able to recreate the exact
|
||||
// context in which the encryption happened.
|
||||
//
|
||||
// For example, it is totally valid for if the protected header's
|
||||
// integrity was calculated using a non-standard line breaks:
|
||||
//
|
||||
// {"a dummy":
|
||||
// "protected header"}
|
||||
//
|
||||
// Once parsed, though, we can only serialize the protected header as:
|
||||
//
|
||||
// {"a dummy":"protected header"}
|
||||
//
|
||||
// which would obviously result in a contradicting integrity value
|
||||
// if we tried to re-calculate it from a parsed message.
|
||||
//
|
||||
//nolint:govet
|
||||
type Message struct {
|
||||
// Comments on each field are taken from https://datatracker.ietf.org/doc/html/rfc7516
|
||||
//
|
||||
// protected
|
||||
// The "protected" member MUST be present and contain the value
|
||||
// BASE64URL(UTF8(JWE Protected Header)) when the JWE Protected
|
||||
// Header value is non-empty; otherwise, it MUST be absent. These
|
||||
// Header Parameter values are integrity protected.
|
||||
protectedHeaders Headers
|
||||
|
||||
// unprotected
|
||||
// The "unprotected" member MUST be present and contain the value JWE
|
||||
// Shared Unprotected Header when the JWE Shared Unprotected Header
|
||||
// value is non-empty; otherwise, it MUST be absent. This value is
|
||||
// represented as an unencoded JSON object, rather than as a string.
|
||||
// These Header Parameter values are not integrity protected.
|
||||
//
|
||||
// JWX note: This field is NOT mutually exclusive with per-recipient
|
||||
// headers within the implementation because... it's too much work.
|
||||
// It is _never_ populated (we don't provide a way to do this) upon encryption.
|
||||
// When decrypting, if present its values are always merged with
|
||||
// per-recipient header.
|
||||
unprotectedHeaders Headers
|
||||
|
||||
// iv
|
||||
// The "iv" member MUST be present and contain the value
|
||||
// BASE64URL(JWE Initialization Vector) when the JWE Initialization
|
||||
// Vector value is non-empty; otherwise, it MUST be absent.
|
||||
initializationVector []byte
|
||||
|
||||
// aad
|
||||
// The "aad" member MUST be present and contain the value
|
||||
// BASE64URL(JWE AAD)) when the JWE AAD value is non-empty;
|
||||
// otherwise, it MUST be absent. A JWE AAD value can be included to
|
||||
// supply a base64url-encoded value to be integrity protected but not
|
||||
// encrypted.
|
||||
authenticatedData []byte
|
||||
|
||||
// ciphertext
|
||||
// The "ciphertext" member MUST be present and contain the value
|
||||
// BASE64URL(JWE Ciphertext).
|
||||
cipherText []byte
|
||||
|
||||
// tag
|
||||
// The "tag" member MUST be present and contain the value
|
||||
// BASE64URL(JWE Authentication Tag) when the JWE Authentication Tag
|
||||
// value is non-empty; otherwise, it MUST be absent.
|
||||
tag []byte
|
||||
|
||||
// recipients
|
||||
// The "recipients" member value MUST be an array of JSON objects.
|
||||
// Each object contains information specific to a single recipient.
|
||||
// This member MUST be present with exactly one array element per
|
||||
// recipient, even if some or all of the array element values are the
|
||||
// empty JSON object "{}" (which can happen when all Header Parameter
|
||||
// values are shared between all recipients and when no encrypted key
|
||||
// is used, such as when doing Direct Encryption).
|
||||
//
|
||||
// Some Header Parameters, including the "alg" parameter, can be shared
|
||||
// among all recipient computations. Header Parameters in the JWE
|
||||
// Protected Header and JWE Shared Unprotected Header values are shared
|
||||
// among all recipients.
|
||||
//
|
||||
// The Header Parameter values used when creating or validating per-
|
||||
// recipient ciphertext and Authentication Tag values are the union of
|
||||
// the three sets of Header Parameter values that may be present: (1)
|
||||
// the JWE Protected Header represented in the "protected" member, (2)
|
||||
// the JWE Shared Unprotected Header represented in the "unprotected"
|
||||
// member, and (3) the JWE Per-Recipient Unprotected Header represented
|
||||
// in the "header" member of the recipient's array element. The union
|
||||
// of these sets of Header Parameters comprises the JOSE Header. The
|
||||
// Header Parameter names in the three locations MUST be disjoint.
|
||||
recipients []Recipient
|
||||
|
||||
// TODO: Additional members can be present in both the JSON objects defined
|
||||
// above; if not understood by implementations encountering them, they
|
||||
// MUST be ignored.
|
||||
// privateParams map[string]any
|
||||
|
||||
// These two fields below are not available for the public consumers of this object.
|
||||
// rawProtectedHeaders stores the original protected header buffer
|
||||
rawProtectedHeaders []byte
|
||||
// storeProtectedHeaders is a hint to be used in UnmarshalJSON().
|
||||
// When this flag is true, UnmarshalJSON() will populate the
|
||||
// rawProtectedHeaders field
|
||||
storeProtectedHeaders bool
|
||||
}
|
||||
|
||||
// populater is an interface for things that may modify the
|
||||
// JWE header. e.g. ByteWithECPrivateKey
|
||||
type populater interface {
|
||||
Populate(keygen.Setter) error
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "aescbc",
|
||||
srcs = ["aescbc.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = ["//internal/pool"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "aescbc_test",
|
||||
srcs = ["aescbc_test.go"],
|
||||
embed = [":aescbc"],
|
||||
deps = ["@com_github_stretchr_testify//require"]
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":aescbc",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
)
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
package aescbc
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
NonceSize = 16
|
||||
)
|
||||
|
||||
const defaultBufSize int64 = 256 * 1024 * 1024
|
||||
|
||||
var maxBufSize atomic.Int64
|
||||
|
||||
func init() {
|
||||
SetMaxBufferSize(defaultBufSize)
|
||||
}
|
||||
|
||||
func SetMaxBufferSize(siz int64) {
|
||||
if siz <= 0 {
|
||||
siz = defaultBufSize
|
||||
}
|
||||
maxBufSize.Store(siz)
|
||||
}
|
||||
|
||||
func pad(buf []byte, n int) []byte {
|
||||
rem := n - len(buf)%n
|
||||
if rem == 0 {
|
||||
return buf
|
||||
}
|
||||
|
||||
bufsiz := len(buf) + rem
|
||||
mbs := maxBufSize.Load()
|
||||
if int64(bufsiz) > mbs {
|
||||
panic(fmt.Errorf("failed to allocate buffer"))
|
||||
}
|
||||
newbuf := make([]byte, bufsiz)
|
||||
copy(newbuf, buf)
|
||||
|
||||
for i := len(buf); i < len(newbuf); i++ {
|
||||
newbuf[i] = byte(rem)
|
||||
}
|
||||
return newbuf
|
||||
}
|
||||
|
||||
// ref. https://github.com/golang/go/blob/c3db64c0f45e8f2d75c5b59401e0fc925701b6f4/src/crypto/tls/conn.go#L279-L324
|
||||
//
|
||||
// extractPadding returns, in constant time, the length of the padding to remove
|
||||
// from the end of payload. It also returns a byte which is equal to 255 if the
|
||||
// padding was valid and 0 otherwise. See RFC 2246, Section 6.2.3.2.
|
||||
func extractPadding(payload []byte) (toRemove int, good byte) {
|
||||
if len(payload) < 1 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
paddingLen := payload[len(payload)-1]
|
||||
t := uint(len(payload)) - uint(paddingLen)
|
||||
// if len(payload) > paddingLen then the MSB of t is zero
|
||||
good = byte(int32(^t) >> 31)
|
||||
|
||||
// The maximum possible padding length plus the actual length field
|
||||
toCheck := 256
|
||||
// The length of the padded data is public, so we can use an if here
|
||||
toCheck = min(toCheck, len(payload))
|
||||
|
||||
for i := 1; i <= toCheck; i++ {
|
||||
t := uint(paddingLen) - uint(i)
|
||||
// if i <= paddingLen then the MSB of t is zero
|
||||
mask := byte(int32(^t) >> 31)
|
||||
b := payload[len(payload)-i]
|
||||
good &^= mask&paddingLen ^ mask&b
|
||||
}
|
||||
|
||||
// We AND together the bits of good and replicate the result across
|
||||
// all the bits.
|
||||
good &= good << 4
|
||||
good &= good << 2
|
||||
good &= good << 1
|
||||
good = uint8(int8(good) >> 7)
|
||||
|
||||
// Zero the padding length on error. This ensures any unchecked bytes
|
||||
// are included in the MAC. Otherwise, an attacker that could
|
||||
// distinguish MAC failures from padding failures could mount an attack
|
||||
// similar to POODLE in SSL 3.0: given a good ciphertext that uses a
|
||||
// full block's worth of padding, replace the final block with another
|
||||
// block. If the MAC check passed but the padding check failed, the
|
||||
// last byte of that block decrypted to the block size.
|
||||
//
|
||||
// See also macAndPaddingGood logic below.
|
||||
paddingLen &= good
|
||||
|
||||
toRemove = int(paddingLen)
|
||||
return
|
||||
}
|
||||
|
||||
type Hmac struct {
|
||||
blockCipher cipher.Block
|
||||
hash func() hash.Hash
|
||||
keysize int
|
||||
tagsize int
|
||||
integrityKey []byte
|
||||
}
|
||||
|
||||
type BlockCipherFunc func([]byte) (cipher.Block, error)
|
||||
|
||||
func New(key []byte, f BlockCipherFunc) (hmac *Hmac, err error) {
|
||||
keysize := len(key) / 2
|
||||
ikey := key[:keysize]
|
||||
ekey := key[keysize:]
|
||||
|
||||
bc, ciphererr := f(ekey)
|
||||
if ciphererr != nil {
|
||||
err = fmt.Errorf(`failed to execute block cipher function: %w`, ciphererr)
|
||||
return
|
||||
}
|
||||
|
||||
var hfunc func() hash.Hash
|
||||
switch keysize {
|
||||
case 16:
|
||||
hfunc = sha256.New
|
||||
case 24:
|
||||
hfunc = sha512.New384
|
||||
case 32:
|
||||
hfunc = sha512.New
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported key size %d", keysize)
|
||||
}
|
||||
|
||||
return &Hmac{
|
||||
blockCipher: bc,
|
||||
hash: hfunc,
|
||||
integrityKey: ikey,
|
||||
keysize: keysize,
|
||||
tagsize: keysize, // NonceSize,
|
||||
// While investigating GH #207, I stumbled upon another problem where
|
||||
// the computed tags don't match on decrypt. After poking through the
|
||||
// code using a bunch of debug statements, I've finally found out that
|
||||
// tagsize = keysize makes the whole thing work.
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NonceSize fulfills the crypto.AEAD interface
|
||||
func (c Hmac) NonceSize() int {
|
||||
return NonceSize
|
||||
}
|
||||
|
||||
// Overhead fulfills the crypto.AEAD interface
|
||||
func (c Hmac) Overhead() int {
|
||||
return c.blockCipher.BlockSize() + c.tagsize
|
||||
}
|
||||
|
||||
func (c Hmac) ComputeAuthTag(aad, nonce, ciphertext []byte) ([]byte, error) {
|
||||
var buf [8]byte
|
||||
binary.BigEndian.PutUint64(buf[:], uint64(len(aad)*8))
|
||||
|
||||
h := hmac.New(c.hash, c.integrityKey)
|
||||
|
||||
// compute the tag
|
||||
// no need to check errors because Write never returns an error: https://pkg.go.dev/hash#Hash
|
||||
//
|
||||
// > Write (via the embedded io.Writer interface) adds more data to the running hash.
|
||||
// > It never returns an error.
|
||||
h.Write(aad)
|
||||
h.Write(nonce)
|
||||
h.Write(ciphertext)
|
||||
h.Write(buf[:])
|
||||
s := h.Sum(nil)
|
||||
return s[:c.tagsize], nil
|
||||
}
|
||||
|
||||
func ensureSize(dst []byte, n int) []byte {
|
||||
// if the dst buffer has enough length just copy the relevant parts to it.
|
||||
// Otherwise create a new slice that's big enough, and operate on that
|
||||
// Note: I think go-jose has a bug in that it checks for cap(), but not len().
|
||||
ret := dst
|
||||
if diff := n - len(dst); diff > 0 {
|
||||
// dst is not big enough
|
||||
ret = make([]byte, n)
|
||||
copy(ret, dst)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Seal fulfills the crypto.AEAD interface
|
||||
func (c Hmac) Seal(dst, nonce, plaintext, data []byte) []byte {
|
||||
ctlen := len(plaintext)
|
||||
bufsiz := ctlen + c.Overhead()
|
||||
mbs := maxBufSize.Load()
|
||||
|
||||
if int64(bufsiz) > mbs {
|
||||
panic(fmt.Errorf("failed to allocate buffer"))
|
||||
}
|
||||
ciphertext := make([]byte, bufsiz)[:ctlen]
|
||||
copy(ciphertext, plaintext)
|
||||
ciphertext = pad(ciphertext, c.blockCipher.BlockSize())
|
||||
|
||||
cbc := cipher.NewCBCEncrypter(c.blockCipher, nonce)
|
||||
cbc.CryptBlocks(ciphertext, ciphertext)
|
||||
|
||||
authtag, err := c.ComputeAuthTag(data, nonce, ciphertext)
|
||||
if err != nil {
|
||||
// Hmac implements cipher.AEAD interface. Seal can't return error.
|
||||
// But currently it never reach here because of Hmac.ComputeAuthTag doesn't return error.
|
||||
panic(fmt.Errorf("failed to seal on hmac: %v", err))
|
||||
}
|
||||
|
||||
retlen := len(dst) + len(ciphertext) + len(authtag)
|
||||
|
||||
ret := ensureSize(dst, retlen)
|
||||
out := ret[len(dst):]
|
||||
n := copy(out, ciphertext)
|
||||
copy(out[n:], authtag)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Open fulfills the crypto.AEAD interface
|
||||
func (c Hmac) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
|
||||
if len(ciphertext) < c.keysize {
|
||||
return nil, fmt.Errorf(`invalid ciphertext (too short)`)
|
||||
}
|
||||
|
||||
tagOffset := len(ciphertext) - c.tagsize
|
||||
if tagOffset%c.blockCipher.BlockSize() != 0 {
|
||||
return nil, fmt.Errorf(
|
||||
"invalid ciphertext (invalid length: %d %% %d != 0)",
|
||||
tagOffset,
|
||||
c.blockCipher.BlockSize(),
|
||||
)
|
||||
}
|
||||
tag := ciphertext[tagOffset:]
|
||||
ciphertext = ciphertext[:tagOffset]
|
||||
|
||||
expectedTag, err := c.ComputeAuthTag(data, nonce, ciphertext[:tagOffset])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to compute auth tag: %w`, err)
|
||||
}
|
||||
|
||||
cbc := cipher.NewCBCDecrypter(c.blockCipher, nonce)
|
||||
buf := pool.ByteSlice().GetCapacity(tagOffset)
|
||||
defer pool.ByteSlice().Put(buf)
|
||||
buf = buf[:tagOffset]
|
||||
|
||||
cbc.CryptBlocks(buf, ciphertext)
|
||||
|
||||
toRemove, good := extractPadding(buf)
|
||||
cmp := subtle.ConstantTimeCompare(expectedTag, tag) & int(good)
|
||||
if cmp != 1 {
|
||||
return nil, errors.New(`invalid ciphertext`)
|
||||
}
|
||||
|
||||
plaintext := buf[:len(buf)-toRemove]
|
||||
ret := ensureSize(dst, len(plaintext))
|
||||
out := ret[len(dst):]
|
||||
copy(out, plaintext)
|
||||
return ret, nil
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "cipher",
|
||||
srcs = [
|
||||
"cipher.go",
|
||||
"interface.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/cipher",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//jwa",
|
||||
"//jwe/internal/aescbc",
|
||||
"//jwe/internal/keygen",
|
||||
"//internal/tokens",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "cipher_test",
|
||||
srcs = ["cipher_test.go"],
|
||||
deps = [
|
||||
":cipher",
|
||||
"//jwa",
|
||||
"//internal/tokens",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":cipher",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
)
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/aescbc"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
)
|
||||
|
||||
var gcm = &gcmFetcher{}
|
||||
var cbc = &cbcFetcher{}
|
||||
|
||||
func (f gcmFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) {
|
||||
if len(key) != size {
|
||||
return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size)
|
||||
}
|
||||
aescipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`cipher: failed to create AES cipher for GCM: %w`, err)
|
||||
}
|
||||
|
||||
aead, err := cipher.NewGCM(aescipher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create GCM for cipher: %w`, err)
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
func (f cbcFetcher) Fetch(key []byte, size int) (cipher.AEAD, error) {
|
||||
if len(key) != size {
|
||||
return nil, fmt.Errorf(`key size (%d) does not match expected key size (%d)`, len(key), size)
|
||||
}
|
||||
aead, err := aescbc.New(key, aes.NewCipher)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`cipher: failed to create AES cipher for CBC: %w`, err)
|
||||
}
|
||||
return aead, nil
|
||||
}
|
||||
|
||||
func (c AesContentCipher) KeySize() int {
|
||||
return c.keysize
|
||||
}
|
||||
|
||||
func (c AesContentCipher) TagSize() int {
|
||||
return c.tagsize
|
||||
}
|
||||
|
||||
func NewAES(alg string) (*AesContentCipher, error) {
|
||||
var keysize int
|
||||
var tagsize int
|
||||
var fetcher Fetcher
|
||||
switch alg {
|
||||
case tokens.A128GCM:
|
||||
keysize = 16
|
||||
tagsize = 16
|
||||
fetcher = gcm
|
||||
case tokens.A192GCM:
|
||||
keysize = 24
|
||||
tagsize = 16
|
||||
fetcher = gcm
|
||||
case tokens.A256GCM:
|
||||
keysize = 32
|
||||
tagsize = 16
|
||||
fetcher = gcm
|
||||
case tokens.A128CBC_HS256:
|
||||
tagsize = 16
|
||||
keysize = tagsize * 2
|
||||
fetcher = cbc
|
||||
case tokens.A192CBC_HS384:
|
||||
tagsize = 24
|
||||
keysize = tagsize * 2
|
||||
fetcher = cbc
|
||||
case tokens.A256CBC_HS512:
|
||||
tagsize = 32
|
||||
keysize = tagsize * 2
|
||||
fetcher = cbc
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to create AES content cipher: invalid algorithm (%s)", alg)
|
||||
}
|
||||
|
||||
return &AesContentCipher{
|
||||
keysize: keysize,
|
||||
tagsize: tagsize,
|
||||
fetch: fetcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c AesContentCipher) Encrypt(cek, plaintext, aad []byte) (iv, ciphertxt, tag []byte, err error) {
|
||||
var aead cipher.AEAD
|
||||
aead, err = c.fetch.Fetch(cek, c.keysize)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(`failed to fetch AEAD: %w`, err)
|
||||
}
|
||||
|
||||
// Seal may panic (argh!), so protect ourselves from that
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
switch e := e.(type) {
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf("%s", e)
|
||||
}
|
||||
err = fmt.Errorf(`failed to encrypt: %w`, err)
|
||||
}
|
||||
}()
|
||||
|
||||
if c.NonceGenerator != nil {
|
||||
iv, err = c.NonceGenerator(aead.NonceSize())
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(`failed to generate nonce: %w`, err)
|
||||
}
|
||||
} else {
|
||||
bs, err := keygen.Random(aead.NonceSize())
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(`failed to generate random nonce: %w`, err)
|
||||
}
|
||||
iv = bs.Bytes()
|
||||
}
|
||||
|
||||
combined := aead.Seal(nil, iv, plaintext, aad)
|
||||
tagoffset := len(combined) - c.TagSize()
|
||||
|
||||
if tagoffset < 0 {
|
||||
panic(fmt.Sprintf("tag offset is less than 0 (combined len = %d, tagsize = %d)", len(combined), c.TagSize()))
|
||||
}
|
||||
|
||||
tag = combined[tagoffset:]
|
||||
ciphertxt = make([]byte, tagoffset)
|
||||
copy(ciphertxt, combined[:tagoffset])
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c AesContentCipher) Decrypt(cek, iv, ciphertxt, tag, aad []byte) (plaintext []byte, err error) {
|
||||
aead, err := c.fetch.Fetch(cek, c.keysize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to fetch AEAD data: %w`, err)
|
||||
}
|
||||
|
||||
// Open may panic (argh!), so protect ourselves from that
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
switch e := e.(type) {
|
||||
case error:
|
||||
err = e
|
||||
default:
|
||||
err = fmt.Errorf(`%s`, e)
|
||||
}
|
||||
err = fmt.Errorf(`failed to decrypt: %w`, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
combined := make([]byte, len(ciphertxt)+len(tag))
|
||||
copy(combined, ciphertxt)
|
||||
copy(combined[len(ciphertxt):], tag)
|
||||
|
||||
buf, aeaderr := aead.Open(nil, iv, combined, aad)
|
||||
if aeaderr != nil {
|
||||
err = fmt.Errorf(`aead.Open failed: %w`, aeaderr)
|
||||
return
|
||||
}
|
||||
plaintext = buf
|
||||
return
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package cipher
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
)
|
||||
|
||||
const (
|
||||
TagSize = 16
|
||||
)
|
||||
|
||||
// ContentCipher knows how to encrypt/decrypt the content given a content
|
||||
// encryption key and other data
|
||||
type ContentCipher interface {
|
||||
KeySize() int
|
||||
Encrypt(cek, aad, plaintext []byte) ([]byte, []byte, []byte, error)
|
||||
Decrypt(cek, iv, aad, ciphertext, tag []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
type Fetcher interface {
|
||||
Fetch([]byte, int) (cipher.AEAD, error)
|
||||
}
|
||||
|
||||
type gcmFetcher struct{}
|
||||
type cbcFetcher struct{}
|
||||
|
||||
// AesContentCipher represents a cipher based on AES
|
||||
type AesContentCipher struct {
|
||||
NonceGenerator func(int) ([]byte, error)
|
||||
fetch Fetcher
|
||||
keysize int
|
||||
tagsize int
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "concatkdf",
|
||||
srcs = ["concatkdf.go"],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf",
|
||||
visibility = ["//:__subpackages__"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "concatkdf_test",
|
||||
srcs = ["concatkdf_test.go"],
|
||||
embed = [":concatkdf"],
|
||||
deps = [
|
||||
"//jwa",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":concatkdf",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
)
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package concatkdf
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type KDF struct {
|
||||
buf []byte
|
||||
otherinfo []byte
|
||||
z []byte
|
||||
hash crypto.Hash
|
||||
}
|
||||
|
||||
func ndata(src []byte) []byte {
|
||||
buf := make([]byte, 4+len(src))
|
||||
binary.BigEndian.PutUint32(buf, uint32(len(src)))
|
||||
copy(buf[4:], src)
|
||||
return buf
|
||||
}
|
||||
|
||||
func New(hash crypto.Hash, alg, Z, apu, apv, pubinfo, privinfo []byte) *KDF {
|
||||
algbuf := ndata(alg)
|
||||
apubuf := ndata(apu)
|
||||
apvbuf := ndata(apv)
|
||||
|
||||
concat := make([]byte, len(algbuf)+len(apubuf)+len(apvbuf)+len(pubinfo)+len(privinfo))
|
||||
n := copy(concat, algbuf)
|
||||
n += copy(concat[n:], apubuf)
|
||||
n += copy(concat[n:], apvbuf)
|
||||
n += copy(concat[n:], pubinfo)
|
||||
copy(concat[n:], privinfo)
|
||||
|
||||
return &KDF{
|
||||
hash: hash,
|
||||
otherinfo: concat,
|
||||
z: Z,
|
||||
}
|
||||
}
|
||||
|
||||
func (k *KDF) Read(out []byte) (int, error) {
|
||||
var round uint32 = 1
|
||||
h := k.hash.New()
|
||||
|
||||
for len(out) > len(k.buf) {
|
||||
h.Reset()
|
||||
|
||||
if err := binary.Write(h, binary.BigEndian, round); err != nil {
|
||||
return 0, fmt.Errorf(`failed to write round using kdf: %w`, err)
|
||||
}
|
||||
if _, err := h.Write(k.z); err != nil {
|
||||
return 0, fmt.Errorf(`failed to write z using kdf: %w`, err)
|
||||
}
|
||||
if _, err := h.Write(k.otherinfo); err != nil {
|
||||
return 0, fmt.Errorf(`failed to write other info using kdf: %w`, err)
|
||||
}
|
||||
|
||||
k.buf = append(k.buf, h.Sum(nil)...)
|
||||
round++
|
||||
}
|
||||
|
||||
n := copy(out, k.buf[:len(out)])
|
||||
k.buf = k.buf[len(out):]
|
||||
return n, nil
|
||||
}
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "content_crypt",
|
||||
srcs = [
|
||||
"content_crypt.go",
|
||||
"interface.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//jwa",
|
||||
"//jwe/internal/cipher",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":content_crypt",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
)
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
package content_crypt //nolint:golint
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
|
||||
)
|
||||
|
||||
func (c Generic) Algorithm() jwa.ContentEncryptionAlgorithm {
|
||||
return c.alg
|
||||
}
|
||||
|
||||
func (c Generic) Encrypt(cek, plaintext, aad []byte) ([]byte, []byte, []byte, error) {
|
||||
iv, encrypted, tag, err := c.cipher.Encrypt(cek, plaintext, aad)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf(`failed to crypt content: %w`, err)
|
||||
}
|
||||
|
||||
return iv, encrypted, tag, nil
|
||||
}
|
||||
|
||||
func (c Generic) Decrypt(cek, iv, ciphertext, tag, aad []byte) ([]byte, error) {
|
||||
return c.cipher.Decrypt(cek, iv, ciphertext, tag, aad)
|
||||
}
|
||||
|
||||
func NewGeneric(alg jwa.ContentEncryptionAlgorithm) (*Generic, error) {
|
||||
c, err := cipher.NewAES(alg.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`aes crypt: failed to create content cipher: %w`, err)
|
||||
}
|
||||
|
||||
return &Generic{
|
||||
alg: alg,
|
||||
cipher: c,
|
||||
keysize: c.KeySize(),
|
||||
tagsize: 16,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c Generic) KeySize() int {
|
||||
return c.keysize
|
||||
}
|
||||
Generated
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
package content_crypt //nolint:golint
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
|
||||
)
|
||||
|
||||
// Generic encrypts a message by applying all the necessary
|
||||
// modifications to the keys and the contents
|
||||
type Generic struct {
|
||||
alg jwa.ContentEncryptionAlgorithm
|
||||
keysize int
|
||||
tagsize int
|
||||
cipher cipher.ContentCipher
|
||||
}
|
||||
|
||||
type Cipher interface {
|
||||
Decrypt([]byte, []byte, []byte, []byte, []byte) ([]byte, error)
|
||||
KeySize() int
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
load("@rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "keygen",
|
||||
srcs = [
|
||||
"interface.go",
|
||||
"keygen.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/internal/keygen",
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/ecutil",
|
||||
"//jwa",
|
||||
"//jwe/internal/concatkdf",
|
||||
"//internal/tokens",
|
||||
"//jwk",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":keygen",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package keygen
|
||||
|
||||
// ByteKey is a generated key that only has the key's byte buffer
|
||||
// as its instance data. If a key needs to do more, such as providing
|
||||
// values to be set in a JWE header, that key type wraps a ByteKey
|
||||
type ByteKey []byte
|
||||
|
||||
// ByteWithECPublicKey holds the EC private key that generated
|
||||
// the key along with the key itself. This is required to set the
|
||||
// proper values in the JWE headers
|
||||
type ByteWithECPublicKey struct {
|
||||
ByteKey
|
||||
|
||||
PublicKey any
|
||||
}
|
||||
|
||||
type ByteWithIVAndTag struct {
|
||||
ByteKey
|
||||
|
||||
IV []byte
|
||||
Tag []byte
|
||||
}
|
||||
|
||||
type ByteWithSaltAndCount struct {
|
||||
ByteKey
|
||||
|
||||
Salt []byte
|
||||
Count int
|
||||
}
|
||||
|
||||
// ByteSource is an interface for things that return a byte sequence.
|
||||
// This is used for KeyGenerator so that the result of computations can
|
||||
// carry more than just the generate byte sequence.
|
||||
type ByteSource interface {
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
type Setter interface {
|
||||
Set(string, any) error
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package keygen
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/ecutil"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
// Bytes returns the byte from this ByteKey
|
||||
func (k ByteKey) Bytes() []byte {
|
||||
return []byte(k)
|
||||
}
|
||||
|
||||
func Random(n int) (ByteSource, error) {
|
||||
buf := make([]byte, n)
|
||||
if _, err := io.ReadFull(rand.Reader, buf); err != nil {
|
||||
return nil, fmt.Errorf(`failed to read from rand.Reader: %w`, err)
|
||||
}
|
||||
return ByteKey(buf), nil
|
||||
}
|
||||
|
||||
// Ecdhes generates a new key using ECDH-ES
|
||||
func Ecdhes(alg string, enc string, keysize int, pubkey *ecdsa.PublicKey, apu, apv []byte) (ByteSource, error) {
|
||||
priv, err := ecdsa.GenerateKey(pubkey.Curve, rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to generate key for ECDH-ES: %w`, err)
|
||||
}
|
||||
|
||||
var algorithm string
|
||||
if alg == tokens.ECDH_ES {
|
||||
algorithm = enc
|
||||
} else {
|
||||
algorithm = alg
|
||||
}
|
||||
|
||||
pubinfo := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8)
|
||||
|
||||
if !priv.PublicKey.Curve.IsOnCurve(pubkey.X, pubkey.Y) {
|
||||
return nil, fmt.Errorf(`public key used does not contain a point (X,Y) on the curve`)
|
||||
}
|
||||
z, _ := priv.PublicKey.Curve.ScalarMult(pubkey.X, pubkey.Y, priv.D.Bytes())
|
||||
zBytes := ecutil.AllocECPointBuffer(z, priv.PublicKey.Curve)
|
||||
defer ecutil.ReleaseECPointBuffer(zBytes)
|
||||
kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, apu, apv, pubinfo, []byte{})
|
||||
kek := make([]byte, keysize)
|
||||
if _, err := kdf.Read(kek); err != nil {
|
||||
return nil, fmt.Errorf(`failed to read kdf: %w`, err)
|
||||
}
|
||||
|
||||
return ByteWithECPublicKey{
|
||||
PublicKey: &priv.PublicKey,
|
||||
ByteKey: ByteKey(kek),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// X25519 generates a new key using ECDH-ES with X25519
|
||||
func X25519(alg string, enc string, keysize int, pubkey *ecdh.PublicKey) (ByteSource, error) {
|
||||
priv, err := ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to generate key for X25519: %w`, err)
|
||||
}
|
||||
|
||||
var algorithm string
|
||||
if alg == tokens.ECDH_ES {
|
||||
algorithm = enc
|
||||
} else {
|
||||
algorithm = alg
|
||||
}
|
||||
|
||||
pubinfo := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(pubinfo, uint32(keysize)*8)
|
||||
|
||||
zBytes, err := priv.ECDH(pubkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to compute Z: %w`, err)
|
||||
}
|
||||
kdf := concatkdf.New(crypto.SHA256, []byte(algorithm), zBytes, []byte{}, []byte{}, pubinfo, []byte{})
|
||||
kek := make([]byte, keysize)
|
||||
if _, err := kdf.Read(kek); err != nil {
|
||||
return nil, fmt.Errorf(`failed to read kdf: %w`, err)
|
||||
}
|
||||
|
||||
return ByteWithECPublicKey{
|
||||
PublicKey: priv.PublicKey(),
|
||||
ByteKey: ByteKey(kek),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HeaderPopulate populates the header with the required EC-DSA public key
|
||||
// information ('epk' key)
|
||||
func (k ByteWithECPublicKey) Populate(h Setter) error {
|
||||
key, err := jwk.Import(k.PublicKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to create JWK: %w`, err)
|
||||
}
|
||||
|
||||
if err := h.Set("epk", key); err != nil {
|
||||
return fmt.Errorf(`failed to write header: %w`, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HeaderPopulate populates the header with the required AES GCM
|
||||
// parameters ('iv' and 'tag')
|
||||
func (k ByteWithIVAndTag) Populate(h Setter) error {
|
||||
if err := h.Set("iv", k.IV); err != nil {
|
||||
return fmt.Errorf(`failed to write header: %w`, err)
|
||||
}
|
||||
|
||||
if err := h.Set("tag", k.Tag); err != nil {
|
||||
return fmt.Errorf(`failed to write header: %w`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HeaderPopulate populates the header with the required PBES2
|
||||
// parameters ('p2s' and 'p2c')
|
||||
func (k ByteWithSaltAndCount) Populate(h Setter) error {
|
||||
if err := h.Set("p2c", k.Count); err != nil {
|
||||
return fmt.Errorf(`failed to write header: %w`, err)
|
||||
}
|
||||
|
||||
if err := h.Set("p2s", k.Salt); err != nil {
|
||||
return fmt.Errorf(`failed to write header: %w`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
// Code generated by tools/cmd/genreadfile/main.go. DO NOT EDIT.
|
||||
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
type sysFS struct{}
|
||||
|
||||
func (sysFS) Open(path string) (fs.File, error) {
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
func ReadFile(path string, options ...ReadFileOption) (*Message, error) {
|
||||
|
||||
var srcFS fs.FS = sysFS{}
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identFS{}:
|
||||
if err := option.Value(&srcFS); err != nil {
|
||||
return nil, fmt.Errorf("failed to set fs.FS: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f, err := srcFS.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
return ParseReader(f)
|
||||
}
|
||||
+1118
File diff suppressed because it is too large
Load Diff
+43
@@ -0,0 +1,43 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "jwebb",
|
||||
srcs = [
|
||||
"content_cipher.go",
|
||||
"key_decrypt_asymmetric.go",
|
||||
"key_decrypt_symmetric.go",
|
||||
"key_encrypt_asymmetric.go",
|
||||
"key_encrypt_symmetric.go",
|
||||
"key_encryption.go",
|
||||
"keywrap.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwe/jwebb",
|
||||
visibility = ["//jwe:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/keyconv",
|
||||
"//internal/pool",
|
||||
"//jwe/internal/cipher",
|
||||
"//jwe/internal/concatkdf",
|
||||
"//jwe/internal/content_crypt",
|
||||
"//jwe/internal/keygen",
|
||||
"//internal/tokens",
|
||||
"@org_golang_x_crypto//pbkdf2",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwebb_test",
|
||||
srcs = [
|
||||
"decrypt_test.go",
|
||||
"jwebb_test.go",
|
||||
"keywrap_test.go",
|
||||
],
|
||||
embed = [":jwebb"],
|
||||
deps = [
|
||||
"//internal/jwxtest",
|
||||
"//jwa",
|
||||
"//jwe/internal/keygen",
|
||||
"//internal/tokens",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/cipher"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/content_crypt"
|
||||
)
|
||||
|
||||
// ContentEncryptionIsSupported checks if the content encryption algorithm is supported
|
||||
func ContentEncryptionIsSupported(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.A128GCM, tokens.A192GCM, tokens.A256GCM,
|
||||
tokens.A128CBC_HS256, tokens.A192CBC_HS384, tokens.A256CBC_HS512:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// CreateContentCipher creates a content encryption cipher for the given algorithm string
|
||||
func CreateContentCipher(alg string) (content_crypt.Cipher, error) {
|
||||
if !ContentEncryptionIsSupported(alg) {
|
||||
return nil, fmt.Errorf(`invalid content cipher algorithm (%s)`, alg)
|
||||
}
|
||||
|
||||
cipher, err := cipher.NewAES(alg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to build content cipher for %s: %w`, alg, err)
|
||||
}
|
||||
|
||||
return cipher, nil
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Package jwebb provides the building blocks (hence the name "bb") for JWE operations.
|
||||
// It should be thought of as a low-level API, almost akin to internal packages
|
||||
// that should not be used directly by users of the jwx package. However, these exist
|
||||
// to provide a more efficient way to perform JWE operations without the overhead of
|
||||
// the higher-level jwe package to power-users who know what they are doing.
|
||||
//
|
||||
// This package is currently considered EXPERIMENTAL, and the API may change
|
||||
// without notice. It is not recommended to use this package unless you are
|
||||
// fully aware of the implications of using it.
|
||||
//
|
||||
// All bb packages in jwx follow the same design principles:
|
||||
// 1. Does minimal checking of input parameters (for performance); callers need to ensure that the parameters are valid.
|
||||
// 2. All exported functions are stringly typed (i.e. they do not take any parameters unless they absolutely have to).
|
||||
// 3. Does not rely on other public jwx packages (they are standalone, except for internal packages).
|
||||
package jwebb
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/keyconv"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/concatkdf"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
)
|
||||
|
||||
func contentEncryptionKeySize(ctalg string) (uint32, error) {
|
||||
switch ctalg {
|
||||
case tokens.A128GCM:
|
||||
return tokens.KeySize16, nil
|
||||
case tokens.A192GCM:
|
||||
return tokens.KeySize24, nil
|
||||
case tokens.A256GCM:
|
||||
return tokens.KeySize32, nil
|
||||
case tokens.A128CBC_HS256:
|
||||
return tokens.KeySize32, nil
|
||||
case tokens.A192CBC_HS384:
|
||||
return tokens.KeySize48, nil
|
||||
case tokens.A256CBC_HS512:
|
||||
return tokens.KeySize64, nil
|
||||
default:
|
||||
return 0, fmt.Errorf(`unsupported content encryption algorithm %s`, ctalg)
|
||||
}
|
||||
}
|
||||
|
||||
func KeyEncryptionECDHESKeySize(alg, ctalg string) (string, uint32, bool, error) {
|
||||
switch alg {
|
||||
case tokens.ECDH_ES:
|
||||
keysize, err := contentEncryptionKeySize(ctalg)
|
||||
if err != nil {
|
||||
return "", 0, false, err
|
||||
}
|
||||
return ctalg, keysize, false, nil
|
||||
case tokens.ECDH_ES_A128KW:
|
||||
return alg, tokens.KeySize16, true, nil
|
||||
case tokens.ECDH_ES_A192KW:
|
||||
return alg, tokens.KeySize24, true, nil
|
||||
case tokens.ECDH_ES_A256KW:
|
||||
return alg, tokens.KeySize32, true, nil
|
||||
default:
|
||||
return "", 0, false, fmt.Errorf(`unsupported key encryption algorithm %s`, alg)
|
||||
}
|
||||
}
|
||||
|
||||
func DeriveECDHES(alg string, apu, apv []byte, privkeyif, pubkeyif any, keysize uint32) ([]byte, error) {
|
||||
pubinfo := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(pubinfo, keysize*tokens.BitsPerByte)
|
||||
|
||||
var privkey *ecdh.PrivateKey
|
||||
var pubkey *ecdh.PublicKey
|
||||
if err := keyconv.ECDHPrivateKey(&privkey, privkeyif); err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
|
||||
}
|
||||
if err := keyconv.ECDHPublicKey(&pubkey, pubkeyif); err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.DeriveECDHES: %w`, err)
|
||||
}
|
||||
|
||||
zBytes, err := privkey.ECDH(pubkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.DeriveECDHES: unable to determine Z: %w`, err)
|
||||
}
|
||||
kdf := concatkdf.New(crypto.SHA256, []byte(alg), zBytes, apu, apv, pubinfo, []byte{})
|
||||
key := make([]byte, keysize)
|
||||
if _, err := kdf.Read(key); err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.DeriveECDHES: failed to read kdf: %w`, err)
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func KeyDecryptECDHESKeyWrap(_, enckey []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
|
||||
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create cipher for ECDH-ES key wrap: %w`, err)
|
||||
}
|
||||
|
||||
return Unwrap(block, enckey)
|
||||
}
|
||||
|
||||
func KeyDecryptECDHES(_, _ []byte, alg string, apu, apv []byte, privkey, pubkey any, keysize uint32) ([]byte, error) {
|
||||
key, err := DeriveECDHES(alg, apu, apv, privkey, pubkey, keysize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to derive ECDHES encryption key: %w`, err)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// RSA key decryption functions
|
||||
|
||||
func KeyDecryptRSA15(_, enckey []byte, privkeyif any, keysize int) ([]byte, error) {
|
||||
var privkey *rsa.PrivateKey
|
||||
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.KeyDecryptRSA15: %w`, err)
|
||||
}
|
||||
|
||||
// Perform some input validation.
|
||||
expectedlen := privkey.PublicKey.N.BitLen() / tokens.BitsPerByte
|
||||
if expectedlen != len(enckey) {
|
||||
// Input size is incorrect, the encrypted payload should always match
|
||||
// the size of the public modulus (e.g. using a 2048 bit key will
|
||||
// produce 256 bytes of output). Reject this since it's invalid input.
|
||||
return nil, fmt.Errorf(
|
||||
"input size for key decrypt is incorrect (expected %d, got %d)",
|
||||
expectedlen,
|
||||
len(enckey),
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a random CEK of the required size
|
||||
bk, err := keygen.Random(keysize * tokens.RSAKeyGenMultiplier)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to generate key`)
|
||||
}
|
||||
cek := bk.Bytes()
|
||||
|
||||
// Use a defer/recover pattern to handle potential panics from DecryptPKCS1v15SessionKey
|
||||
defer func() {
|
||||
// DecryptPKCS1v15SessionKey sometimes panics on an invalid payload
|
||||
// because of an index out of bounds error, which we want to ignore.
|
||||
// This has been fixed in Go 1.3.1 (released 2014/08/13), the recover()
|
||||
// only exists for preventing crashes with unpatched versions.
|
||||
// See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k
|
||||
// See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33
|
||||
_ = recover()
|
||||
}()
|
||||
|
||||
// When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to
|
||||
// prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing
|
||||
// the Million Message Attack on Cryptographic Message Syntax". We are
|
||||
// therefore deliberately ignoring errors here.
|
||||
_ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, privkey, enckey, cek)
|
||||
|
||||
return cek, nil
|
||||
}
|
||||
|
||||
func KeyDecryptRSAOAEP(_, enckey []byte, alg string, privkeyif any) ([]byte, error) {
|
||||
var privkey *rsa.PrivateKey
|
||||
if err := keyconv.RSAPrivateKey(&privkey, privkeyif); err != nil {
|
||||
return nil, fmt.Errorf(`jwebb.KeyDecryptRSAOAEP: %w`, err)
|
||||
}
|
||||
|
||||
var hash hash.Hash
|
||||
switch alg {
|
||||
case tokens.RSA_OAEP:
|
||||
hash = sha1.New()
|
||||
case tokens.RSA_OAEP_256:
|
||||
hash = sha256.New()
|
||||
case tokens.RSA_OAEP_384:
|
||||
hash = sha512.New384()
|
||||
case tokens.RSA_OAEP_512:
|
||||
hash = sha512.New()
|
||||
default:
|
||||
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
|
||||
}
|
||||
|
||||
return rsa.DecryptOAEP(hash, rand.Reader, privkey, enckey, []byte{})
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
cryptocipher "crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
// AES key wrap decryption functions
|
||||
|
||||
// Use constants from tokens package
|
||||
// No need to redefine them here
|
||||
|
||||
func KeyDecryptAESKW(_, enckey []byte, _ string, sharedkey []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(sharedkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
|
||||
}
|
||||
|
||||
cek, err := Unwrap(block, enckey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to unwrap data: %w`, err)
|
||||
}
|
||||
return cek, nil
|
||||
}
|
||||
|
||||
func KeyDecryptDirect(_, _ []byte, _ string, cek []byte) ([]byte, error) {
|
||||
return cek, nil
|
||||
}
|
||||
|
||||
func KeyDecryptPBES2(_, enckey []byte, alg string, password []byte, salt []byte, count int) ([]byte, error) {
|
||||
var hashFunc func() hash.Hash
|
||||
var keylen int
|
||||
|
||||
switch alg {
|
||||
case tokens.PBES2_HS256_A128KW:
|
||||
hashFunc = sha256.New
|
||||
keylen = tokens.KeySize16
|
||||
case tokens.PBES2_HS384_A192KW:
|
||||
hashFunc = sha512.New384
|
||||
keylen = tokens.KeySize24
|
||||
case tokens.PBES2_HS512_A256KW:
|
||||
hashFunc = sha512.New
|
||||
keylen = tokens.KeySize32
|
||||
default:
|
||||
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
|
||||
}
|
||||
|
||||
// Derive key using PBKDF2
|
||||
derivedKey := pbkdf2.Key(password, salt, count, keylen, hashFunc)
|
||||
|
||||
// Use the derived key for AES key wrap
|
||||
return KeyDecryptAESKW(nil, enckey, alg, derivedKey)
|
||||
}
|
||||
|
||||
func KeyDecryptAESGCMKW(recipientKey, _ []byte, _ string, sharedkey []byte, iv []byte, tag []byte) ([]byte, error) {
|
||||
if len(iv) != tokens.GCMIVSize {
|
||||
return nil, fmt.Errorf("GCM requires 96-bit iv, got %d", len(iv)*tokens.BitsPerByte)
|
||||
}
|
||||
if len(tag) != tokens.GCMTagSize {
|
||||
return nil, fmt.Errorf("GCM requires 128-bit tag, got %d", len(tag)*tokens.BitsPerByte)
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(sharedkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
|
||||
}
|
||||
|
||||
aesgcm, err := cryptocipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
|
||||
}
|
||||
|
||||
// Combine recipient key and tag for GCM decryption
|
||||
ciphertext := recipientKey[:]
|
||||
ciphertext = append(ciphertext, tag...)
|
||||
|
||||
jek, err := aesgcm.Open(nil, iv, ciphertext, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to decode key: %w`, err)
|
||||
}
|
||||
|
||||
return jek, nil
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
)
|
||||
|
||||
// KeyEncryptRSA15 encrypts the CEK using RSA PKCS#1 v1.5
|
||||
func KeyEncryptRSA15(cek []byte, _ string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
|
||||
encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, pubkey, cek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to encrypt using PKCS1v15: %w`, err)
|
||||
}
|
||||
return keygen.ByteKey(encrypted), nil
|
||||
}
|
||||
|
||||
// KeyEncryptRSAOAEP encrypts the CEK using RSA OAEP
|
||||
func KeyEncryptRSAOAEP(cek []byte, alg string, pubkey *rsa.PublicKey) (keygen.ByteSource, error) {
|
||||
var hash hash.Hash
|
||||
switch alg {
|
||||
case tokens.RSA_OAEP:
|
||||
hash = sha1.New()
|
||||
case tokens.RSA_OAEP_256:
|
||||
hash = sha256.New()
|
||||
case tokens.RSA_OAEP_384:
|
||||
hash = sha512.New384()
|
||||
case tokens.RSA_OAEP_512:
|
||||
hash = sha512.New()
|
||||
default:
|
||||
return nil, fmt.Errorf(`failed to generate key encrypter for RSA-OAEP: RSA_OAEP/RSA_OAEP_256/RSA_OAEP_384/RSA_OAEP_512 required`)
|
||||
}
|
||||
|
||||
encrypted, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, cek, []byte{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to OAEP encrypt: %w`, err)
|
||||
}
|
||||
return keygen.ByteKey(encrypted), nil
|
||||
}
|
||||
|
||||
// generateECDHESKeyECDSA generates the key material for ECDSA keys using ECDH-ES
|
||||
func generateECDHESKeyECDSA(alg string, calg string, keysize uint32, pubkey *ecdsa.PublicKey, apu, apv []byte) (keygen.ByteWithECPublicKey, error) {
|
||||
// Generate the key directly
|
||||
kg, err := keygen.Ecdhes(alg, calg, int(keysize), pubkey, apu, apv)
|
||||
if err != nil {
|
||||
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate ECDSA key: %w`, err)
|
||||
}
|
||||
|
||||
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
|
||||
if !ok {
|
||||
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
|
||||
}
|
||||
|
||||
return bwpk, nil
|
||||
}
|
||||
|
||||
// generateECDHESKeyX25519 generates the key material for X25519 keys using ECDH-ES
|
||||
func generateECDHESKeyX25519(alg string, calg string, keysize uint32, pubkey *ecdh.PublicKey) (keygen.ByteWithECPublicKey, error) {
|
||||
// Generate the key directly
|
||||
kg, err := keygen.X25519(alg, calg, int(keysize), pubkey)
|
||||
if err != nil {
|
||||
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`failed to generate X25519 key: %w`, err)
|
||||
}
|
||||
|
||||
bwpk, ok := kg.(keygen.ByteWithECPublicKey)
|
||||
if !ok {
|
||||
return keygen.ByteWithECPublicKey{}, fmt.Errorf(`key generator generated invalid key (expected ByteWithECPublicKey)`)
|
||||
}
|
||||
|
||||
return bwpk, nil
|
||||
}
|
||||
|
||||
// KeyEncryptECDHESKeyWrapECDSA encrypts the CEK using ECDH-ES with key wrapping for ECDSA keys
|
||||
func KeyEncryptECDHESKeyWrapECDSA(cek []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
|
||||
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For key wrapping algorithms, wrap the CEK with the generated key
|
||||
block, err := aes.NewCipher(bwpk.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
|
||||
}
|
||||
|
||||
jek, err := Wrap(block, cek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
|
||||
}
|
||||
|
||||
bwpk.ByteKey = keygen.ByteKey(jek)
|
||||
return bwpk, nil
|
||||
}
|
||||
|
||||
// KeyEncryptECDHESKeyWrapX25519 encrypts the CEK using ECDH-ES with key wrapping for X25519 keys
|
||||
func KeyEncryptECDHESKeyWrapX25519(cek []byte, alg string, _ []byte, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
|
||||
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For key wrapping algorithms, wrap the CEK with the generated key
|
||||
block, err := aes.NewCipher(bwpk.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to generate cipher from generated key: %w`, err)
|
||||
}
|
||||
|
||||
jek, err := Wrap(block, cek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
|
||||
}
|
||||
|
||||
bwpk.ByteKey = keygen.ByteKey(jek)
|
||||
return bwpk, nil
|
||||
}
|
||||
|
||||
// KeyEncryptECDHESECDSA encrypts using ECDH-ES direct (no key wrapping) for ECDSA keys
|
||||
func KeyEncryptECDHESECDSA(_ []byte, alg string, apu, apv []byte, pubkey *ecdsa.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
|
||||
bwpk, err := generateECDHESKeyECDSA(alg, calg, keysize, pubkey, apu, apv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For direct ECDH-ES, return the generated key directly
|
||||
return bwpk, nil
|
||||
}
|
||||
|
||||
// KeyEncryptECDHESX25519 encrypts using ECDH-ES direct (no key wrapping) for X25519 keys
|
||||
func KeyEncryptECDHESX25519(_ []byte, alg string, _, _ []byte, pubkey *ecdh.PublicKey, keysize uint32, calg string) (keygen.ByteSource, error) {
|
||||
bwpk, err := generateECDHESKeyX25519(alg, calg, keysize, pubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// For direct ECDH-ES, return the generated key directly
|
||||
return bwpk, nil
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
cryptocipher "crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
"github.com/lestrrat-go/jwx/v3/jwe/internal/keygen"
|
||||
)
|
||||
|
||||
// KeyEncryptAESKW encrypts the CEK using AES key wrap
|
||||
func KeyEncryptAESKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
|
||||
block, err := aes.NewCipher(sharedkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
|
||||
}
|
||||
|
||||
encrypted, err := Wrap(block, cek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
|
||||
}
|
||||
return keygen.ByteKey(encrypted), nil
|
||||
}
|
||||
|
||||
// KeyEncryptDirect returns the CEK directly for DIRECT algorithm
|
||||
func KeyEncryptDirect(_ []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
|
||||
return keygen.ByteKey(sharedkey), nil
|
||||
}
|
||||
|
||||
// KeyEncryptPBES2 encrypts the CEK using PBES2 password-based encryption
|
||||
func KeyEncryptPBES2(cek []byte, alg string, password []byte) (keygen.ByteSource, error) {
|
||||
var hashFunc func() hash.Hash
|
||||
var keylen int
|
||||
|
||||
switch alg {
|
||||
case tokens.PBES2_HS256_A128KW:
|
||||
hashFunc = sha256.New
|
||||
keylen = tokens.KeySize16
|
||||
case tokens.PBES2_HS384_A192KW:
|
||||
hashFunc = sha512.New384
|
||||
keylen = tokens.KeySize24
|
||||
case tokens.PBES2_HS512_A256KW:
|
||||
hashFunc = sha512.New
|
||||
keylen = tokens.KeySize32
|
||||
default:
|
||||
return nil, fmt.Errorf(`unsupported PBES2 algorithm: %s`, alg)
|
||||
}
|
||||
|
||||
count := tokens.PBES2DefaultIterations
|
||||
salt := make([]byte, keylen)
|
||||
_, err := io.ReadFull(rand.Reader, salt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to get random salt: %w`, err)
|
||||
}
|
||||
|
||||
fullsalt := []byte(alg)
|
||||
fullsalt = append(fullsalt, byte(tokens.PBES2NullByteSeparator))
|
||||
fullsalt = append(fullsalt, salt...)
|
||||
|
||||
// Derive key using PBKDF2
|
||||
derivedKey := pbkdf2.Key(password, fullsalt, count, keylen, hashFunc)
|
||||
|
||||
// Use the derived key for AES key wrap
|
||||
block, err := aes.NewCipher(derivedKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create cipher from derived key: %w`, err)
|
||||
}
|
||||
encrypted, err := Wrap(block, cek)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to wrap data: %w`, err)
|
||||
}
|
||||
|
||||
return keygen.ByteWithSaltAndCount{
|
||||
ByteKey: encrypted,
|
||||
Salt: salt,
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KeyEncryptAESGCMKW encrypts the CEK using AES GCM key wrap
|
||||
func KeyEncryptAESGCMKW(cek []byte, _ string, sharedkey []byte) (keygen.ByteSource, error) {
|
||||
block, err := aes.NewCipher(sharedkey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create new AES cipher: %w`, err)
|
||||
}
|
||||
|
||||
aesgcm, err := cryptocipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to create new GCM wrap: %w`, err)
|
||||
}
|
||||
|
||||
iv := make([]byte, aesgcm.NonceSize())
|
||||
_, err = io.ReadFull(rand.Reader, iv)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to get random iv: %w`, err)
|
||||
}
|
||||
|
||||
encrypted := aesgcm.Seal(nil, iv, cek, nil)
|
||||
tag := encrypted[len(encrypted)-aesgcm.Overhead():]
|
||||
ciphertext := encrypted[:len(encrypted)-aesgcm.Overhead()]
|
||||
|
||||
return keygen.ByteWithIVAndTag{
|
||||
ByteKey: ciphertext,
|
||||
IV: iv,
|
||||
Tag: tag,
|
||||
}, nil
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
// IsECDHES checks if the algorithm is an ECDH-ES based algorithm
|
||||
func IsECDHES(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.ECDH_ES, tokens.ECDH_ES_A128KW, tokens.ECDH_ES_A192KW, tokens.ECDH_ES_A256KW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsRSA15 checks if the algorithm is RSA1_5
|
||||
func IsRSA15(alg string) bool {
|
||||
return alg == tokens.RSA1_5
|
||||
}
|
||||
|
||||
// IsRSAOAEP checks if the algorithm is an RSA-OAEP based algorithm
|
||||
func IsRSAOAEP(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.RSA_OAEP, tokens.RSA_OAEP_256, tokens.RSA_OAEP_384, tokens.RSA_OAEP_512:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsAESKW checks if the algorithm is an AES key wrap algorithm
|
||||
func IsAESKW(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.A128KW, tokens.A192KW, tokens.A256KW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsAESGCMKW checks if the algorithm is an AES-GCM key wrap algorithm
|
||||
func IsAESGCMKW(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.A128GCMKW, tokens.A192GCMKW, tokens.A256GCMKW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsPBES2 checks if the algorithm is a PBES2 based algorithm
|
||||
func IsPBES2(alg string) bool {
|
||||
switch alg {
|
||||
case tokens.PBES2_HS256_A128KW, tokens.PBES2_HS384_A192KW, tokens.PBES2_HS512_A256KW:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// IsDirect checks if the algorithm is direct encryption
|
||||
func IsDirect(alg string) bool {
|
||||
return alg == tokens.DIRECT
|
||||
}
|
||||
|
||||
// IsSymmetric checks if the algorithm is a symmetric key encryption algorithm
|
||||
func IsSymmetric(alg string) bool {
|
||||
return IsAESKW(alg) || IsAESGCMKW(alg) || IsPBES2(alg) || IsDirect(alg)
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package jwebb
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
var keywrapDefaultIV = []byte{0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6}
|
||||
|
||||
func Wrap(kek cipher.Block, cek []byte) ([]byte, error) {
|
||||
if len(cek)%tokens.KeywrapBlockSize != 0 {
|
||||
return nil, fmt.Errorf(`keywrap input must be %d byte blocks`, tokens.KeywrapBlockSize)
|
||||
}
|
||||
|
||||
n := len(cek) / tokens.KeywrapChunkLen
|
||||
r := make([][]byte, n)
|
||||
|
||||
for i := range n {
|
||||
r[i] = make([]byte, tokens.KeywrapChunkLen)
|
||||
copy(r[i], cek[i*tokens.KeywrapChunkLen:])
|
||||
}
|
||||
|
||||
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
|
||||
defer pool.ByteSlice().Put(buffer)
|
||||
// the byte slice has the capacity, but len is 0
|
||||
buffer = buffer[:tokens.KeywrapChunkLen*2]
|
||||
|
||||
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
|
||||
defer pool.ByteSlice().Put(tBytes)
|
||||
// the byte slice has the capacity, but len is 0
|
||||
tBytes = tBytes[:tokens.KeywrapChunkLen]
|
||||
|
||||
copy(buffer, keywrapDefaultIV)
|
||||
|
||||
for t := range tokens.KeywrapRounds * n {
|
||||
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
|
||||
|
||||
kek.Encrypt(buffer, buffer)
|
||||
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||
|
||||
for i := range tokens.KeywrapChunkLen {
|
||||
buffer[i] = buffer[i] ^ tBytes[i]
|
||||
}
|
||||
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
|
||||
}
|
||||
|
||||
out := make([]byte, (n+1)*tokens.KeywrapChunkLen)
|
||||
copy(out, buffer[:tokens.KeywrapChunkLen])
|
||||
for i := range r {
|
||||
copy(out[(i+1)*tokens.KeywrapBlockSize:], r[i])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func Unwrap(block cipher.Block, ciphertxt []byte) ([]byte, error) {
|
||||
if len(ciphertxt)%tokens.KeywrapChunkLen != 0 {
|
||||
return nil, fmt.Errorf(`keyunwrap input must be %d byte blocks`, tokens.KeywrapChunkLen)
|
||||
}
|
||||
|
||||
n := (len(ciphertxt) / tokens.KeywrapChunkLen) - 1
|
||||
r := make([][]byte, n)
|
||||
|
||||
for i := range r {
|
||||
r[i] = make([]byte, tokens.KeywrapChunkLen)
|
||||
copy(r[i], ciphertxt[(i+1)*tokens.KeywrapChunkLen:])
|
||||
}
|
||||
|
||||
buffer := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen * 2)
|
||||
defer pool.ByteSlice().Put(buffer)
|
||||
// the byte slice has the capacity, but len is 0
|
||||
buffer = buffer[:tokens.KeywrapChunkLen*2]
|
||||
|
||||
tBytes := pool.ByteSlice().GetCapacity(tokens.KeywrapChunkLen)
|
||||
defer pool.ByteSlice().Put(tBytes)
|
||||
// the byte slice has the capacity, but len is 0
|
||||
tBytes = tBytes[:tokens.KeywrapChunkLen]
|
||||
|
||||
copy(buffer[:tokens.KeywrapChunkLen], ciphertxt[:tokens.KeywrapChunkLen])
|
||||
|
||||
for t := tokens.KeywrapRounds*n - 1; t >= 0; t-- {
|
||||
binary.BigEndian.PutUint64(tBytes, uint64(t+1))
|
||||
|
||||
for i := range tokens.KeywrapChunkLen {
|
||||
buffer[i] = buffer[i] ^ tBytes[i]
|
||||
}
|
||||
copy(buffer[tokens.KeywrapChunkLen:], r[t%n])
|
||||
|
||||
block.Decrypt(buffer, buffer)
|
||||
|
||||
copy(r[t%n], buffer[tokens.KeywrapChunkLen:])
|
||||
}
|
||||
|
||||
if subtle.ConstantTimeCompare(buffer[:tokens.KeywrapChunkLen], keywrapDefaultIV) == 0 {
|
||||
return nil, fmt.Errorf(`key unwrap: failed to unwrap key`)
|
||||
}
|
||||
|
||||
out := make([]byte, n*tokens.KeywrapChunkLen)
|
||||
for i := range r {
|
||||
copy(out[i*tokens.KeywrapChunkLen:], r[i])
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
// KeyProvider is responsible for providing key(s) to encrypt or decrypt a payload.
|
||||
// Multiple `jwe.KeyProvider`s can be passed to `jwe.Encrypt()` or `jwe.Decrypt()`
|
||||
//
|
||||
// `jwe.Encrypt()` can only accept static key providers via `jwe.WithKey()`,
|
||||
// while `jwe.Decrypt()` can accept `jwe.WithKey()`, `jwe.WithKeySet()`,
|
||||
// and `jwe.WithKeyProvider()`.
|
||||
//
|
||||
// Understanding how this works is crucial to learn how this package works.
|
||||
// Here we will use `jwe.Decrypt()` as an example to show how the `KeyProvider`
|
||||
// works.
|
||||
//
|
||||
// `jwe.Encrypt()` is straightforward: the content encryption key is encrypted
|
||||
// using the provided keys, and JWS recipient objects are created for each.
|
||||
//
|
||||
// `jwe.Decrypt()` is a bit more involved, because there are cases you
|
||||
// will want to compute/deduce/guess the keys that you would like to
|
||||
// use for decryption.
|
||||
//
|
||||
// The first thing that `jwe.Decrypt()` needs to do is to collect the
|
||||
// KeyProviders from the option list that the user provided (presented in pseudocode):
|
||||
//
|
||||
// keyProviders := filterKeyProviders(options)
|
||||
//
|
||||
// Then, remember that a JWE message may contain multiple recipients in the
|
||||
// message. For each recipient, we call on the KeyProviders to give us
|
||||
// the key(s) to use on this CEK:
|
||||
//
|
||||
// for r in msg.Recipients {
|
||||
// for kp in keyProviders {
|
||||
// kp.FetchKeys(ctx, sink, r, msg)
|
||||
// ...
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The `sink` argument passed to the KeyProvider is a temporary storage
|
||||
// for the keys (either a jwk.Key or a "raw" key). The `KeyProvider`
|
||||
// is responsible for sending keys into the `sink`.
|
||||
//
|
||||
// When called, the `KeyProvider` created by `jwe.WithKey()` sends the same key,
|
||||
// `jwe.WithKeySet()` sends keys that matches a particular `kid` and `alg`,
|
||||
// and finally `jwe.WithKeyProvider()` allows you to execute arbitrary
|
||||
// logic to provide keys. If you are providing a custom `KeyProvider`,
|
||||
// you should execute the necessary checks or retrieval of keys, and
|
||||
// then send the key(s) to the sink:
|
||||
//
|
||||
// sink.Key(alg, key)
|
||||
//
|
||||
// These keys are then retrieved and tried for each recipient, until
|
||||
// a match is found:
|
||||
//
|
||||
// keys := sink.Keys()
|
||||
// for key in keys {
|
||||
// if decryptJWEKey(recipient.EncryptedKey(), key) {
|
||||
// return OK
|
||||
// }
|
||||
// }
|
||||
type KeyProvider interface {
|
||||
FetchKeys(context.Context, KeySink, Recipient, *Message) error
|
||||
}
|
||||
|
||||
// KeySink is a data storage where `jwe.KeyProvider` objects should
|
||||
// send their keys to.
|
||||
type KeySink interface {
|
||||
Key(jwa.KeyEncryptionAlgorithm, any)
|
||||
}
|
||||
|
||||
type algKeyPair struct {
|
||||
alg jwa.KeyAlgorithm
|
||||
key any
|
||||
}
|
||||
|
||||
type algKeySink struct {
|
||||
mu sync.Mutex
|
||||
list []algKeyPair
|
||||
}
|
||||
|
||||
func (s *algKeySink) Key(alg jwa.KeyEncryptionAlgorithm, key any) {
|
||||
s.mu.Lock()
|
||||
s.list = append(s.list, algKeyPair{alg, key})
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
type staticKeyProvider struct {
|
||||
alg jwa.KeyEncryptionAlgorithm
|
||||
key any
|
||||
}
|
||||
|
||||
func (kp *staticKeyProvider) FetchKeys(_ context.Context, sink KeySink, _ Recipient, _ *Message) error {
|
||||
sink.Key(kp.alg, kp.key)
|
||||
return nil
|
||||
}
|
||||
|
||||
type keySetProvider struct {
|
||||
set jwk.Set
|
||||
requireKid bool
|
||||
}
|
||||
|
||||
func (kp *keySetProvider) selectKey(sink KeySink, key jwk.Key, _ Recipient, _ *Message) error {
|
||||
if usage, ok := key.KeyUsage(); ok {
|
||||
if usage != "" && usage != jwk.ForEncryption.String() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := key.Algorithm(); ok {
|
||||
kalg, ok := jwa.LookupKeyEncryptionAlgorithm(v.String())
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid key encryption algorithm %s`, v)
|
||||
}
|
||||
|
||||
sink.Key(kalg, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kp *keySetProvider) FetchKeys(_ context.Context, sink KeySink, r Recipient, msg *Message) error {
|
||||
if kp.requireKid {
|
||||
var key jwk.Key
|
||||
|
||||
wantedKid, ok := r.Headers().KeyID()
|
||||
if !ok || wantedKid == "" {
|
||||
return fmt.Errorf(`failed to find matching key: no key ID ("kid") specified in token but multiple keys available in key set`)
|
||||
}
|
||||
// Otherwise we better be able to look up the key, baby.
|
||||
v, ok := kp.set.LookupKeyID(wantedKid)
|
||||
if !ok {
|
||||
return fmt.Errorf(`failed to find key with key ID %q in key set`, wantedKid)
|
||||
}
|
||||
key = v
|
||||
|
||||
return kp.selectKey(sink, key, r, msg)
|
||||
}
|
||||
|
||||
for i := range kp.set.Len() {
|
||||
key, _ := kp.set.Key(i)
|
||||
if err := kp.selectKey(sink, key, r, msg); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyProviderFunc is a type of KeyProvider that is implemented by
|
||||
// a single function. You can use this to create ad-hoc `KeyProvider`
|
||||
// instances.
|
||||
type KeyProviderFunc func(context.Context, KeySink, Recipient, *Message) error
|
||||
|
||||
func (kp KeyProviderFunc) FetchKeys(ctx context.Context, sink KeySink, r Recipient, msg *Message) error {
|
||||
return kp(ctx, sink, r, msg)
|
||||
}
|
||||
+560
@@ -0,0 +1,560 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/base64"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
||||
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
||||
)
|
||||
|
||||
// NewRecipient creates a Recipient object
|
||||
func NewRecipient() Recipient {
|
||||
return &stdRecipient{
|
||||
headers: NewHeaders(),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *stdRecipient) SetHeaders(h Headers) error {
|
||||
r.headers = h
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stdRecipient) SetEncryptedKey(v []byte) error {
|
||||
r.encryptedKey = v
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stdRecipient) Headers() Headers {
|
||||
return r.headers
|
||||
}
|
||||
|
||||
func (r *stdRecipient) EncryptedKey() []byte {
|
||||
return r.encryptedKey
|
||||
}
|
||||
|
||||
type recipientMarshalProxy struct {
|
||||
Headers Headers `json:"header"`
|
||||
EncryptedKey string `json:"encrypted_key"`
|
||||
}
|
||||
|
||||
func (r *stdRecipient) UnmarshalJSON(buf []byte) error {
|
||||
var proxy recipientMarshalProxy
|
||||
proxy.Headers = NewHeaders()
|
||||
if err := json.Unmarshal(buf, &proxy); err != nil {
|
||||
return fmt.Errorf(`failed to unmarshal json into recipient: %w`, err)
|
||||
}
|
||||
|
||||
r.headers = proxy.Headers
|
||||
decoded, err := base64.DecodeString(proxy.EncryptedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode "encrypted_key": %w`, err)
|
||||
}
|
||||
r.encryptedKey = decoded
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *stdRecipient) MarshalJSON() ([]byte, error) {
|
||||
buf := pool.BytesBuffer().Get()
|
||||
defer pool.BytesBuffer().Put(buf)
|
||||
|
||||
buf.WriteString(`{"header":`)
|
||||
hdrbuf, err := json.Marshal(r.headers)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to marshal recipient header: %w`, err)
|
||||
}
|
||||
buf.Write(hdrbuf)
|
||||
buf.WriteString(`,"encrypted_key":"`)
|
||||
buf.WriteString(base64.EncodeToString(r.encryptedKey))
|
||||
buf.WriteString(`"}`)
|
||||
|
||||
ret := make([]byte, buf.Len())
|
||||
copy(ret, buf.Bytes())
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// NewMessage creates a new message
|
||||
func NewMessage() *Message {
|
||||
return &Message{}
|
||||
}
|
||||
|
||||
func (m *Message) AuthenticatedData() []byte {
|
||||
return m.authenticatedData
|
||||
}
|
||||
|
||||
func (m *Message) CipherText() []byte {
|
||||
return m.cipherText
|
||||
}
|
||||
|
||||
func (m *Message) InitializationVector() []byte {
|
||||
return m.initializationVector
|
||||
}
|
||||
|
||||
func (m *Message) Tag() []byte {
|
||||
return m.tag
|
||||
}
|
||||
|
||||
func (m *Message) ProtectedHeaders() Headers {
|
||||
return m.protectedHeaders
|
||||
}
|
||||
|
||||
func (m *Message) Recipients() []Recipient {
|
||||
return m.recipients
|
||||
}
|
||||
|
||||
func (m *Message) UnprotectedHeaders() Headers {
|
||||
return m.unprotectedHeaders
|
||||
}
|
||||
|
||||
const (
|
||||
AuthenticatedDataKey = "aad"
|
||||
CipherTextKey = "ciphertext"
|
||||
CountKey = "p2c"
|
||||
InitializationVectorKey = "iv"
|
||||
ProtectedHeadersKey = "protected"
|
||||
RecipientsKey = "recipients"
|
||||
SaltKey = "p2s"
|
||||
TagKey = "tag"
|
||||
UnprotectedHeadersKey = "unprotected"
|
||||
HeadersKey = "header"
|
||||
EncryptedKeyKey = "encrypted_key"
|
||||
)
|
||||
|
||||
func (m *Message) Set(k string, v any) error {
|
||||
switch k {
|
||||
case AuthenticatedDataKey:
|
||||
buf, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, AuthenticatedDataKey)
|
||||
}
|
||||
m.authenticatedData = buf
|
||||
case CipherTextKey:
|
||||
buf, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, CipherTextKey)
|
||||
}
|
||||
m.cipherText = buf
|
||||
case InitializationVectorKey:
|
||||
buf, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, InitializationVectorKey)
|
||||
}
|
||||
m.initializationVector = buf
|
||||
case ProtectedHeadersKey:
|
||||
cv, ok := v.(Headers)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, ProtectedHeadersKey)
|
||||
}
|
||||
m.protectedHeaders = cv
|
||||
case RecipientsKey:
|
||||
cv, ok := v.([]Recipient)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, RecipientsKey)
|
||||
}
|
||||
m.recipients = cv
|
||||
case TagKey:
|
||||
buf, ok := v.([]byte)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, TagKey)
|
||||
}
|
||||
m.tag = buf
|
||||
case UnprotectedHeadersKey:
|
||||
cv, ok := v.(Headers)
|
||||
if !ok {
|
||||
return fmt.Errorf(`invalid value %T for %s key`, v, UnprotectedHeadersKey)
|
||||
}
|
||||
m.unprotectedHeaders = cv
|
||||
default:
|
||||
if m.unprotectedHeaders == nil {
|
||||
m.unprotectedHeaders = NewHeaders()
|
||||
}
|
||||
return m.unprotectedHeaders.Set(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type messageMarshalProxy struct {
|
||||
AuthenticatedData string `json:"aad,omitempty"`
|
||||
CipherText string `json:"ciphertext"`
|
||||
InitializationVector string `json:"iv,omitempty"`
|
||||
ProtectedHeaders json.RawMessage `json:"protected"`
|
||||
Recipients []json.RawMessage `json:"recipients,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
UnprotectedHeaders Headers `json:"unprotected,omitempty"`
|
||||
|
||||
// For flattened structure. Headers is NOT a Headers type,
|
||||
// so that we can detect its presence by checking proxy.Headers != nil
|
||||
Headers json.RawMessage `json:"header,omitempty"`
|
||||
EncryptedKey string `json:"encrypted_key,omitempty"`
|
||||
}
|
||||
|
||||
type jsonKV struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (m *Message) MarshalJSON() ([]byte, error) {
|
||||
// This is slightly convoluted, but we need to encode the
|
||||
// protected headers, so we do it by hand
|
||||
buf := pool.BytesBuffer().Get()
|
||||
defer pool.BytesBuffer().Put(buf)
|
||||
enc := json.NewEncoder(buf)
|
||||
|
||||
var fields []jsonKV
|
||||
|
||||
if cipherText := m.CipherText(); len(cipherText) > 0 {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(base64.EncodeToString(cipherText)); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, CipherTextKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: CipherTextKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
|
||||
if iv := m.InitializationVector(); len(iv) > 0 {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(base64.EncodeToString(iv)); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, InitializationVectorKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: InitializationVectorKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
|
||||
var encodedProtectedHeaders []byte
|
||||
if h := m.ProtectedHeaders(); h != nil {
|
||||
v, err := h.Encode()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode protected headers: %w`, err)
|
||||
}
|
||||
|
||||
encodedProtectedHeaders = v
|
||||
if len(encodedProtectedHeaders) <= 2 { // '{}'
|
||||
encodedProtectedHeaders = nil
|
||||
} else {
|
||||
fields = append(fields, jsonKV{
|
||||
Key: ProtectedHeadersKey,
|
||||
Value: fmt.Sprintf("%q", encodedProtectedHeaders),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if aad := m.AuthenticatedData(); len(aad) > 0 {
|
||||
aad = base64.Encode(aad)
|
||||
if encodedProtectedHeaders != nil {
|
||||
tmp := append(encodedProtectedHeaders, tokens.Period)
|
||||
aad = append(tmp, aad...)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
if err := enc.Encode(aad); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, AuthenticatedDataKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: AuthenticatedDataKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
|
||||
if recipients := m.Recipients(); len(recipients) > 0 {
|
||||
if len(recipients) == 1 { // Use flattened format
|
||||
if hdrs := recipients[0].Headers(); hdrs != nil {
|
||||
var skipHeaders bool
|
||||
if zeroer, ok := hdrs.(isZeroer); ok {
|
||||
if zeroer.isZero() {
|
||||
skipHeaders = true
|
||||
}
|
||||
}
|
||||
|
||||
if !skipHeaders {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(hdrs); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, HeadersKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: HeadersKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if ek := recipients[0].EncryptedKey(); len(ek) > 0 {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(base64.EncodeToString(ek)); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, EncryptedKeyKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: EncryptedKeyKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(recipients); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, RecipientsKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: RecipientsKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if tag := m.Tag(); len(tag) > 0 {
|
||||
buf.Reset()
|
||||
if err := enc.Encode(base64.EncodeToString(tag)); err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode %s field: %w`, TagKey, err)
|
||||
}
|
||||
fields = append(fields, jsonKV{
|
||||
Key: TagKey,
|
||||
Value: strings.TrimSpace(buf.String()),
|
||||
})
|
||||
}
|
||||
|
||||
if h := m.UnprotectedHeaders(); h != nil {
|
||||
unprotected, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode unprotected headers: %w`, err)
|
||||
}
|
||||
|
||||
if len(unprotected) > 2 {
|
||||
fields = append(fields, jsonKV{
|
||||
Key: UnprotectedHeadersKey,
|
||||
Value: fmt.Sprintf("%q", unprotected),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(fields, func(i, j int) bool {
|
||||
return fields[i].Key < fields[j].Key
|
||||
})
|
||||
buf.Reset()
|
||||
fmt.Fprintf(buf, `{`)
|
||||
for i, kv := range fields {
|
||||
if i > 0 {
|
||||
fmt.Fprintf(buf, `,`)
|
||||
}
|
||||
fmt.Fprintf(buf, `%q:%s`, kv.Key, kv.Value)
|
||||
}
|
||||
fmt.Fprintf(buf, `}`)
|
||||
|
||||
ret := make([]byte, buf.Len())
|
||||
copy(ret, buf.Bytes())
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (m *Message) UnmarshalJSON(buf []byte) error {
|
||||
var proxy messageMarshalProxy
|
||||
proxy.UnprotectedHeaders = NewHeaders()
|
||||
|
||||
if err := json.Unmarshal(buf, &proxy); err != nil {
|
||||
return fmt.Errorf(`failed to unmashal JSON into message: %w`, err)
|
||||
}
|
||||
|
||||
// Get the string value
|
||||
var protectedHeadersStr string
|
||||
if err := json.Unmarshal(proxy.ProtectedHeaders, &protectedHeadersStr); err != nil {
|
||||
return fmt.Errorf(`failed to decode protected headers (1): %w`, err)
|
||||
}
|
||||
|
||||
// It's now in _quoted_ base64 string. Decode it
|
||||
protectedHeadersRaw, err := base64.DecodeString(protectedHeadersStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to base64 decoded protected headers buffer: %w`, err)
|
||||
}
|
||||
|
||||
h := NewHeaders()
|
||||
if err := json.Unmarshal(protectedHeadersRaw, h); err != nil {
|
||||
return fmt.Errorf(`failed to decode protected headers (2): %w`, err)
|
||||
}
|
||||
|
||||
// if this were a flattened message, we would see a "header" and "ciphertext"
|
||||
// field. TODO: do both of these conditions need to meet, or just one?
|
||||
if proxy.Headers != nil || len(proxy.EncryptedKey) > 0 {
|
||||
recipient := NewRecipient()
|
||||
|
||||
// `"heders"` could be empty. If that's the case, just skip the
|
||||
// following unmarshaling step
|
||||
if proxy.Headers != nil {
|
||||
hdrs := NewHeaders()
|
||||
if err := json.Unmarshal(proxy.Headers, hdrs); err != nil {
|
||||
return fmt.Errorf(`failed to decode headers field: %w`, err)
|
||||
}
|
||||
|
||||
if err := recipient.SetHeaders(hdrs); err != nil {
|
||||
return fmt.Errorf(`failed to set new headers: %w`, err)
|
||||
}
|
||||
}
|
||||
|
||||
if v := proxy.EncryptedKey; len(v) > 0 {
|
||||
buf, err := base64.DecodeString(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode encrypted key: %w`, err)
|
||||
}
|
||||
if err := recipient.SetEncryptedKey(buf); err != nil {
|
||||
return fmt.Errorf(`failed to set encrypted key: %w`, err)
|
||||
}
|
||||
}
|
||||
|
||||
m.recipients = append(m.recipients, recipient)
|
||||
} else {
|
||||
for i, recipientbuf := range proxy.Recipients {
|
||||
recipient := NewRecipient()
|
||||
if err := json.Unmarshal(recipientbuf, recipient); err != nil {
|
||||
return fmt.Errorf(`failed to decode recipient at index %d: %w`, i, err)
|
||||
}
|
||||
|
||||
m.recipients = append(m.recipients, recipient)
|
||||
}
|
||||
}
|
||||
|
||||
if src := proxy.AuthenticatedData; len(src) > 0 {
|
||||
v, err := base64.DecodeString(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode "aad": %w`, err)
|
||||
}
|
||||
m.authenticatedData = v
|
||||
}
|
||||
|
||||
if src := proxy.CipherText; len(src) > 0 {
|
||||
v, err := base64.DecodeString(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode "ciphertext": %w`, err)
|
||||
}
|
||||
m.cipherText = v
|
||||
}
|
||||
|
||||
if src := proxy.InitializationVector; len(src) > 0 {
|
||||
v, err := base64.DecodeString(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode "iv": %w`, err)
|
||||
}
|
||||
m.initializationVector = v
|
||||
}
|
||||
|
||||
if src := proxy.Tag; len(src) > 0 {
|
||||
v, err := base64.DecodeString(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode "tag": %w`, err)
|
||||
}
|
||||
m.tag = v
|
||||
}
|
||||
|
||||
m.protectedHeaders = h
|
||||
if m.storeProtectedHeaders {
|
||||
// this is later used for decryption
|
||||
m.rawProtectedHeaders = base64.Encode(protectedHeadersRaw)
|
||||
}
|
||||
|
||||
if iz, ok := proxy.UnprotectedHeaders.(isZeroer); ok {
|
||||
if !iz.isZero() {
|
||||
m.unprotectedHeaders = proxy.UnprotectedHeaders
|
||||
}
|
||||
}
|
||||
|
||||
if len(m.recipients) == 0 {
|
||||
if err := m.makeDummyRecipient(proxy.EncryptedKey, m.protectedHeaders); err != nil {
|
||||
return fmt.Errorf(`failed to setup recipient: %w`, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) makeDummyRecipient(enckeybuf string, protected Headers) error {
|
||||
// Recipients in this case should not contain the content encryption key,
|
||||
// so move that out
|
||||
hdrs, err := protected.Clone()
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to clone headers: %w`, err)
|
||||
}
|
||||
|
||||
if err := hdrs.Remove(ContentEncryptionKey); err != nil {
|
||||
return fmt.Errorf(`failed to remove %#v from public header: %w`, ContentEncryptionKey, err)
|
||||
}
|
||||
|
||||
enckey, err := base64.DecodeString(enckeybuf)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to decode encrypted key: %w`, err)
|
||||
}
|
||||
|
||||
if err := m.Set(RecipientsKey, []Recipient{
|
||||
&stdRecipient{
|
||||
headers: hdrs,
|
||||
encryptedKey: enckey,
|
||||
},
|
||||
}); err != nil {
|
||||
return fmt.Errorf(`failed to set %s: %w`, RecipientsKey, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compact generates a JWE message in compact serialization format from a
|
||||
// `*jwe.Message` object. The object contain exactly one recipient, or
|
||||
// an error is returned.
|
||||
//
|
||||
// This function currently does not take any options, but the function
|
||||
// signature contains `options` for possible future expansion of the API
|
||||
func Compact(m *Message, _ ...CompactOption) ([]byte, error) {
|
||||
if len(m.recipients) != 1 {
|
||||
return nil, fmt.Errorf(`wrong number of recipients for compact serialization`)
|
||||
}
|
||||
|
||||
recipient := m.recipients[0]
|
||||
|
||||
// The protected header must be a merge between the message-wide
|
||||
// protected header AND the recipient header
|
||||
|
||||
// There's something wrong if m.protectedHeaders is nil, but
|
||||
// it could happen
|
||||
if m.protectedHeaders == nil {
|
||||
return nil, fmt.Errorf(`invalid protected header`)
|
||||
}
|
||||
|
||||
hcopy, err := m.protectedHeaders.Clone()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to copy protected header: %w`, err)
|
||||
}
|
||||
hcopy, err = hcopy.Merge(m.unprotectedHeaders)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to merge unprotected header: %w`, err)
|
||||
}
|
||||
hcopy, err = hcopy.Merge(recipient.Headers())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to merge recipient header: %w`, err)
|
||||
}
|
||||
|
||||
protected, err := hcopy.Encode()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to encode header: %w`, err)
|
||||
}
|
||||
|
||||
encryptedKey := base64.Encode(recipient.EncryptedKey())
|
||||
iv := base64.Encode(m.initializationVector)
|
||||
cipher := base64.Encode(m.cipherText)
|
||||
tag := base64.Encode(m.tag)
|
||||
|
||||
buf := pool.BytesBuffer().Get()
|
||||
defer pool.BytesBuffer().Put(buf)
|
||||
|
||||
buf.Grow(len(protected) + len(encryptedKey) + len(iv) + len(cipher) + len(tag) + 4)
|
||||
buf.Write(protected)
|
||||
buf.WriteByte(tokens.Period)
|
||||
buf.Write(encryptedKey)
|
||||
buf.WriteByte(tokens.Period)
|
||||
buf.Write(iv)
|
||||
buf.WriteByte(tokens.Period)
|
||||
buf.Write(cipher)
|
||||
buf.WriteByte(tokens.Period)
|
||||
buf.Write(tag)
|
||||
|
||||
result := make([]byte, buf.Len())
|
||||
copy(result, buf.Bytes())
|
||||
return result, nil
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
"github.com/lestrrat-go/option/v2"
|
||||
)
|
||||
|
||||
// WithProtectedHeaders is used to specify contents of the protected header.
|
||||
// Some fields such as "enc" and "zip" will be overwritten when encryption is
|
||||
// performed.
|
||||
//
|
||||
// There is no equivalent for unprotected headers in this implementation
|
||||
func WithProtectedHeaders(h Headers) EncryptOption {
|
||||
cloned, _ := h.Clone()
|
||||
return &encryptOption{option.New(identProtectedHeaders{}, cloned)}
|
||||
}
|
||||
|
||||
type withKey struct {
|
||||
alg jwa.KeyAlgorithm
|
||||
key any
|
||||
headers Headers
|
||||
}
|
||||
|
||||
type WithKeySuboption interface {
|
||||
Option
|
||||
withKeySuboption()
|
||||
}
|
||||
|
||||
type withKeySuboption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*withKeySuboption) withKeySuboption() {}
|
||||
|
||||
// WithPerRecipientHeaders is used to pass header values for each recipient.
|
||||
// Note that these headers are by definition _unprotected_.
|
||||
func WithPerRecipientHeaders(hdr Headers) WithKeySuboption {
|
||||
return &withKeySuboption{option.New(identPerRecipientHeaders{}, hdr)}
|
||||
}
|
||||
|
||||
// WithKey is used to pass a static algorithm/key pair to either `jwe.Encrypt()` or `jwe.Decrypt()`.
|
||||
// either a raw key or `jwk.Key` may be passed as `key`.
|
||||
//
|
||||
// The `alg` parameter is the identifier for the key encryption algorithm that should be used.
|
||||
// It is of type `jwa.KeyAlgorithm` but in reality you can only pass `jwa.KeyEncryptionAlgorithm`
|
||||
// types. It is this way so that the value in `(jwk.Key).Algorithm()` can be directly
|
||||
// passed to the option. If you specify other algorithm types such as `jwa.SignatureAlgorithm`,
|
||||
// then you will get an error when `jwe.Encrypt()` or `jwe.Decrypt()` is executed.
|
||||
//
|
||||
// Unlike `jwe.WithKeySet()`, the `kid` field does not need to match for the key
|
||||
// to be tried.
|
||||
func WithKey(alg jwa.KeyAlgorithm, key any, options ...WithKeySuboption) EncryptDecryptOption {
|
||||
var hdr Headers
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identPerRecipientHeaders{}:
|
||||
if err := option.Value(&hdr); err != nil {
|
||||
panic(`jwe.WithKey() requires Headers value for WithPerRecipientHeaders option`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &encryptDecryptOption{option.New(identKey{}, &withKey{
|
||||
alg: alg,
|
||||
key: key,
|
||||
headers: hdr,
|
||||
})}
|
||||
}
|
||||
|
||||
func WithKeySet(set jwk.Set, options ...WithKeySetSuboption) DecryptOption {
|
||||
requireKid := true
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identRequireKid{}:
|
||||
if err := option.Value(&requireKid); err != nil {
|
||||
panic(`jwe.WithKeySet() requires bool value for WithRequireKid option`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return WithKeyProvider(&keySetProvider{
|
||||
set: set,
|
||||
requireKid: requireKid,
|
||||
})
|
||||
}
|
||||
|
||||
// WithJSON specifies that the result of `jwe.Encrypt()` is serialized in
|
||||
// JSON format.
|
||||
//
|
||||
// If you pass multiple keys to `jwe.Encrypt()`, it will fail unless
|
||||
// you also pass this option.
|
||||
func WithJSON(options ...WithJSONSuboption) EncryptOption {
|
||||
var pretty bool
|
||||
for _, option := range options {
|
||||
switch option.Ident() {
|
||||
case identPretty{}:
|
||||
if err := option.Value(&pretty); err != nil {
|
||||
panic(`jwe.WithJSON() requires bool value for WithPretty option`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
format := fmtJSON
|
||||
if pretty {
|
||||
format = fmtJSONPretty
|
||||
}
|
||||
return &encryptOption{option.New(identSerialization{}, format)}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package_name: jwe
|
||||
output: jwe/options_gen.go
|
||||
interfaces:
|
||||
- name: GlobalOption
|
||||
comment: |
|
||||
GlobalOption describes options that changes global settings for this package
|
||||
- name: GlobalDecryptOption
|
||||
comment: |
|
||||
GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function
|
||||
methods:
|
||||
- globalOption
|
||||
- decryptOption
|
||||
- name: CompactOption
|
||||
comment: |
|
||||
CompactOption describes options that can be passed to `jwe.Compact`
|
||||
- name: DecryptOption
|
||||
comment: |
|
||||
DecryptOption describes options that can be passed to `jwe.Decrypt`
|
||||
- name: EncryptOption
|
||||
comment: |
|
||||
EncryptOption describes options that can be passed to `jwe.Encrypt`
|
||||
- name: EncryptDecryptOption
|
||||
methods:
|
||||
- encryptOption
|
||||
- decryptOption
|
||||
comment: |
|
||||
EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt`
|
||||
- name: WithJSONSuboption
|
||||
concrete_type: withJSONSuboption
|
||||
comment: |
|
||||
JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option
|
||||
- name: WithKeySetSuboption
|
||||
comment: |
|
||||
WithKeySetSuboption is a suboption passed to the WithKeySet() option
|
||||
- name: ParseOption
|
||||
methods:
|
||||
- readFileOption
|
||||
comment: |
|
||||
ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`
|
||||
- name: ReadFileOption
|
||||
comment: |
|
||||
ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile`
|
||||
options:
|
||||
- ident: Key
|
||||
skip_option: true
|
||||
- ident: Pretty
|
||||
skip_option: true
|
||||
- ident: ProtectedHeaders
|
||||
skip_option: true
|
||||
- ident: PerRecipientHeaders
|
||||
skip_option: true
|
||||
- ident: KeyProvider
|
||||
interface: DecryptOption
|
||||
argument_type: KeyProvider
|
||||
- ident: Context
|
||||
interface: DecryptOption
|
||||
argument_type: context.Context
|
||||
comment: |
|
||||
WithContext specifies the context.Context object to use when decrypting a JWE message.
|
||||
If not provided, context.Background() will be used.
|
||||
- ident: Serialization
|
||||
option_name: WithCompact
|
||||
interface: EncryptOption
|
||||
constant_value: fmtCompact
|
||||
comment: |
|
||||
WithCompact specifies that the result of `jwe.Encrypt()` is serialized in
|
||||
compact format.
|
||||
|
||||
By default `jwe.Encrypt()` will opt to use compact format, so you usually
|
||||
do not need to specify this option other than to be explicit about it
|
||||
- ident: Compress
|
||||
interface: EncryptOption
|
||||
argument_type: jwa.CompressionAlgorithm
|
||||
comment: |
|
||||
WithCompress specifies the compression algorithm to use when encrypting
|
||||
a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF",
|
||||
but the way the specification is written it could allow for more options,
|
||||
and therefore this option takes an argument)
|
||||
- ident: ContentEncryptionAlgorithm
|
||||
interface: EncryptOption
|
||||
option_name: WithContentEncryption
|
||||
argument_type: jwa.ContentEncryptionAlgorithm
|
||||
comment: |
|
||||
WithContentEncryptionAlgorithm specifies the algorithm to encrypt the
|
||||
JWE message content with. If not provided, `jwa.A256GCM` is used.
|
||||
- ident: Message
|
||||
interface: DecryptOption
|
||||
argument_type: '*Message'
|
||||
comment: |
|
||||
WithMessage provides a message object to be populated by `jwe.Decrypt`
|
||||
Using this option allows you to decrypt AND obtain the `jwe.Message`
|
||||
in one go.
|
||||
- ident: RequireKid
|
||||
interface: WithKeySetSuboption
|
||||
argument_type: bool
|
||||
comment: |
|
||||
WithRequiredKid specifies whether the keys in the jwk.Set should
|
||||
only be matched if the target JWE message's Key ID and the Key ID
|
||||
in the given key matches.
|
||||
- ident: Pretty
|
||||
interface: WithJSONSuboption
|
||||
argument_type: bool
|
||||
comment: |
|
||||
WithPretty specifies whether the JSON output should be formatted and
|
||||
indented
|
||||
- ident: MergeProtectedHeaders
|
||||
interface: EncryptOption
|
||||
argument_type: bool
|
||||
comment: |
|
||||
WithMergeProtectedHeaders specify that when given multiple headers
|
||||
as options to `jwe.Encrypt`, these headers should be merged instead
|
||||
of overwritten
|
||||
- ident: FS
|
||||
interface: ReadFileOption
|
||||
argument_type: fs.FS
|
||||
comment: |
|
||||
WithFS specifies the source `fs.FS` object to read the file from.
|
||||
- ident: KeyUsed
|
||||
interface: DecryptOption
|
||||
argument_type: 'any'
|
||||
comment: |
|
||||
WithKeyUsed allows you to specify the `jwe.Decrypt()` function to
|
||||
return the key used for decryption. This may be useful when
|
||||
you specify multiple key sources or if you pass a `jwk.Set`
|
||||
and you want to know which key was successful at decrypting the
|
||||
CEK.
|
||||
|
||||
`v` must be a pointer to an empty `any`. Do not use
|
||||
`jwk.Key` here unless you are 100% sure that all keys that you
|
||||
have provided are instances of `jwk.Key` (remember that the
|
||||
jwx API allows users to specify a raw key such as *rsa.PublicKey)
|
||||
- ident: CEK
|
||||
interface: DecryptOption
|
||||
argument_type: '*[]byte'
|
||||
comment: |
|
||||
WithCEK allows users to specify a variable to store the CEK used in the
|
||||
message upon successful decryption. The variable must be a pointer to
|
||||
a byte slice, and it will only be populated if the decryption is successful.
|
||||
|
||||
This option is currently considered EXPERIMENTAL, and is subject to
|
||||
future changes across minor/micro versions.
|
||||
- ident: MaxPBES2Count
|
||||
interface: GlobalOption
|
||||
argument_type: int
|
||||
comment: |
|
||||
WithMaxPBES2Count specifies the maximum number of PBES2 iterations
|
||||
to use when decrypting a message. If not specified, the default
|
||||
value of 10,000 is used.
|
||||
|
||||
This option has a global effect.
|
||||
- ident: MaxDecompressBufferSize
|
||||
interface: GlobalDecryptOption
|
||||
argument_type: int64
|
||||
comment: |
|
||||
WithMaxDecompressBufferSize specifies the maximum buffer size for used when
|
||||
decompressing the payload of a JWE message. If a compressed JWE payload
|
||||
exceeds this amount when decompressed, jwe.Decrypt will return an error.
|
||||
The default value is 10MB.
|
||||
|
||||
This option can be used for `jwe.Settings()`, which changes the behavior
|
||||
globally, or for `jwe.Decrypt()`, which changes the behavior for that
|
||||
specific call.
|
||||
- ident: CBCBufferSize
|
||||
interface: GlobalOption
|
||||
argument_type: int64
|
||||
comment: |
|
||||
WithCBCBufferSize specifies the maximum buffer size for internal
|
||||
calculations, such as when AES-CBC is performed. The default value is 256MB.
|
||||
If set to an invalid value, the default value is used.
|
||||
In v2, this option was called MaxBufferSize.
|
||||
|
||||
This option has a global effect.
|
||||
- ident: LegacyHeaderMerging
|
||||
interface: EncryptOption
|
||||
argument_type: bool
|
||||
option_name: WithLegacyHeaderMerging
|
||||
comment: |
|
||||
WithLegacyHeaderMerging specifies whether to perform legacy header merging
|
||||
when encrypting a JWE message in JSON serialization, when there is a single recipient.
|
||||
This behavior is enabled by default for backwards compatibility.
|
||||
|
||||
When a JWE message is encrypted in JSON serialization, and there is only
|
||||
one recipient, this library automatically serializes the message in
|
||||
flattened JSON serialization format. In older versions of this library,
|
||||
the protected headers and the per-recipient headers were merged together
|
||||
before computing the AAD (Additional Authenticated Data), but the per-recipient
|
||||
headers were kept as-is in the `header` field of the recipient object.
|
||||
|
||||
This behavior is not compliant with the JWE specification, which states that
|
||||
the headers must be disjoint.
|
||||
|
||||
Passing this option with a value of `false` disables this legacy behavior,
|
||||
and while the per-recipient headers and protected headers are still merged
|
||||
for the purpose of computing AAD, the per-recipient headers are cleared
|
||||
after merging, so that the resulting JWE message is compliant with the
|
||||
specification.
|
||||
|
||||
This option has no effect when there are multiple recipients, or when
|
||||
the serialization format is compact serialization. For multiple recipients
|
||||
(i.e. full JSON serialization), the protected headers and per-recipient
|
||||
headers are never merged, and it is the caller's responsibility to ensure
|
||||
that the headers are disjoint. In compact serialization, there are no per-recipient
|
||||
headers; in fact, the protected headers are the only headers that exist,
|
||||
and therefore there is no possibility of header collision after merging
|
||||
(note: while per-recipient headers do not make sense in compact serialization,
|
||||
this library does not prevent you from setting them -- they are all just
|
||||
merged into the protected headers).
|
||||
|
||||
In future versions, the new behavior will be the default. New users are
|
||||
encouraged to set this option to `false` now to avoid future issues.
|
||||
+392
@@ -0,0 +1,392 @@
|
||||
// Code generated by tools/cmd/genoptions/main.go. DO NOT EDIT.
|
||||
|
||||
package jwe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/fs"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/jwa"
|
||||
"github.com/lestrrat-go/option/v2"
|
||||
)
|
||||
|
||||
type Option = option.Interface
|
||||
|
||||
// CompactOption describes options that can be passed to `jwe.Compact`
|
||||
type CompactOption interface {
|
||||
Option
|
||||
compactOption()
|
||||
}
|
||||
|
||||
type compactOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*compactOption) compactOption() {}
|
||||
|
||||
// DecryptOption describes options that can be passed to `jwe.Decrypt`
|
||||
type DecryptOption interface {
|
||||
Option
|
||||
decryptOption()
|
||||
}
|
||||
|
||||
type decryptOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*decryptOption) decryptOption() {}
|
||||
|
||||
// EncryptDecryptOption describes options that can be passed to either `jwe.Encrypt` or `jwe.Decrypt`
|
||||
type EncryptDecryptOption interface {
|
||||
Option
|
||||
encryptOption()
|
||||
decryptOption()
|
||||
}
|
||||
|
||||
type encryptDecryptOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*encryptDecryptOption) encryptOption() {}
|
||||
|
||||
func (*encryptDecryptOption) decryptOption() {}
|
||||
|
||||
// EncryptOption describes options that can be passed to `jwe.Encrypt`
|
||||
type EncryptOption interface {
|
||||
Option
|
||||
encryptOption()
|
||||
}
|
||||
|
||||
type encryptOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*encryptOption) encryptOption() {}
|
||||
|
||||
// GlobalDecryptOption describes options that changes global settings and for each call of the `jwe.Decrypt` function
|
||||
type GlobalDecryptOption interface {
|
||||
Option
|
||||
globalOption()
|
||||
decryptOption()
|
||||
}
|
||||
|
||||
type globalDecryptOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*globalDecryptOption) globalOption() {}
|
||||
|
||||
func (*globalDecryptOption) decryptOption() {}
|
||||
|
||||
// GlobalOption describes options that changes global settings for this package
|
||||
type GlobalOption interface {
|
||||
Option
|
||||
globalOption()
|
||||
}
|
||||
|
||||
type globalOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*globalOption) globalOption() {}
|
||||
|
||||
// ReadFileOption is a type of `Option` that can be passed to `jwe.Parse`
|
||||
type ParseOption interface {
|
||||
Option
|
||||
readFileOption()
|
||||
}
|
||||
|
||||
type parseOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*parseOption) readFileOption() {}
|
||||
|
||||
// ReadFileOption is a type of `Option` that can be passed to `jwe.ReadFile`
|
||||
type ReadFileOption interface {
|
||||
Option
|
||||
readFileOption()
|
||||
}
|
||||
|
||||
type readFileOption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*readFileOption) readFileOption() {}
|
||||
|
||||
// JSONSuboption describes suboptions that can be passed to `jwe.WithJSON()` option
|
||||
type WithJSONSuboption interface {
|
||||
Option
|
||||
withJSONSuboption()
|
||||
}
|
||||
|
||||
type withJSONSuboption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*withJSONSuboption) withJSONSuboption() {}
|
||||
|
||||
// WithKeySetSuboption is a suboption passed to the WithKeySet() option
|
||||
type WithKeySetSuboption interface {
|
||||
Option
|
||||
withKeySetSuboption()
|
||||
}
|
||||
|
||||
type withKeySetSuboption struct {
|
||||
Option
|
||||
}
|
||||
|
||||
func (*withKeySetSuboption) withKeySetSuboption() {}
|
||||
|
||||
type identCBCBufferSize struct{}
|
||||
type identCEK struct{}
|
||||
type identCompress struct{}
|
||||
type identContentEncryptionAlgorithm struct{}
|
||||
type identContext struct{}
|
||||
type identFS struct{}
|
||||
type identKey struct{}
|
||||
type identKeyProvider struct{}
|
||||
type identKeyUsed struct{}
|
||||
type identLegacyHeaderMerging struct{}
|
||||
type identMaxDecompressBufferSize struct{}
|
||||
type identMaxPBES2Count struct{}
|
||||
type identMergeProtectedHeaders struct{}
|
||||
type identMessage struct{}
|
||||
type identPerRecipientHeaders struct{}
|
||||
type identPretty struct{}
|
||||
type identProtectedHeaders struct{}
|
||||
type identRequireKid struct{}
|
||||
type identSerialization struct{}
|
||||
|
||||
func (identCBCBufferSize) String() string {
|
||||
return "WithCBCBufferSize"
|
||||
}
|
||||
|
||||
func (identCEK) String() string {
|
||||
return "WithCEK"
|
||||
}
|
||||
|
||||
func (identCompress) String() string {
|
||||
return "WithCompress"
|
||||
}
|
||||
|
||||
func (identContentEncryptionAlgorithm) String() string {
|
||||
return "WithContentEncryption"
|
||||
}
|
||||
|
||||
func (identContext) String() string {
|
||||
return "WithContext"
|
||||
}
|
||||
|
||||
func (identFS) String() string {
|
||||
return "WithFS"
|
||||
}
|
||||
|
||||
func (identKey) String() string {
|
||||
return "WithKey"
|
||||
}
|
||||
|
||||
func (identKeyProvider) String() string {
|
||||
return "WithKeyProvider"
|
||||
}
|
||||
|
||||
func (identKeyUsed) String() string {
|
||||
return "WithKeyUsed"
|
||||
}
|
||||
|
||||
func (identLegacyHeaderMerging) String() string {
|
||||
return "WithLegacyHeaderMerging"
|
||||
}
|
||||
|
||||
func (identMaxDecompressBufferSize) String() string {
|
||||
return "WithMaxDecompressBufferSize"
|
||||
}
|
||||
|
||||
func (identMaxPBES2Count) String() string {
|
||||
return "WithMaxPBES2Count"
|
||||
}
|
||||
|
||||
func (identMergeProtectedHeaders) String() string {
|
||||
return "WithMergeProtectedHeaders"
|
||||
}
|
||||
|
||||
func (identMessage) String() string {
|
||||
return "WithMessage"
|
||||
}
|
||||
|
||||
func (identPerRecipientHeaders) String() string {
|
||||
return "WithPerRecipientHeaders"
|
||||
}
|
||||
|
||||
func (identPretty) String() string {
|
||||
return "WithPretty"
|
||||
}
|
||||
|
||||
func (identProtectedHeaders) String() string {
|
||||
return "WithProtectedHeaders"
|
||||
}
|
||||
|
||||
func (identRequireKid) String() string {
|
||||
return "WithRequireKid"
|
||||
}
|
||||
|
||||
func (identSerialization) String() string {
|
||||
return "WithSerialization"
|
||||
}
|
||||
|
||||
// WithCBCBufferSize specifies the maximum buffer size for internal
|
||||
// calculations, such as when AES-CBC is performed. The default value is 256MB.
|
||||
// If set to an invalid value, the default value is used.
|
||||
// In v2, this option was called MaxBufferSize.
|
||||
//
|
||||
// This option has a global effect.
|
||||
func WithCBCBufferSize(v int64) GlobalOption {
|
||||
return &globalOption{option.New(identCBCBufferSize{}, v)}
|
||||
}
|
||||
|
||||
// WithCEK allows users to specify a variable to store the CEK used in the
|
||||
// message upon successful decryption. The variable must be a pointer to
|
||||
// a byte slice, and it will only be populated if the decryption is successful.
|
||||
//
|
||||
// This option is currently considered EXPERIMENTAL, and is subject to
|
||||
// future changes across minor/micro versions.
|
||||
func WithCEK(v *[]byte) DecryptOption {
|
||||
return &decryptOption{option.New(identCEK{}, v)}
|
||||
}
|
||||
|
||||
// WithCompress specifies the compression algorithm to use when encrypting
|
||||
// a payload using `jwe.Encrypt` (Yes, we know it can only be "" or "DEF",
|
||||
// but the way the specification is written it could allow for more options,
|
||||
// and therefore this option takes an argument)
|
||||
func WithCompress(v jwa.CompressionAlgorithm) EncryptOption {
|
||||
return &encryptOption{option.New(identCompress{}, v)}
|
||||
}
|
||||
|
||||
// WithContentEncryptionAlgorithm specifies the algorithm to encrypt the
|
||||
// JWE message content with. If not provided, `jwa.A256GCM` is used.
|
||||
func WithContentEncryption(v jwa.ContentEncryptionAlgorithm) EncryptOption {
|
||||
return &encryptOption{option.New(identContentEncryptionAlgorithm{}, v)}
|
||||
}
|
||||
|
||||
// WithContext specifies the context.Context object to use when decrypting a JWE message.
|
||||
// If not provided, context.Background() will be used.
|
||||
func WithContext(v context.Context) DecryptOption {
|
||||
return &decryptOption{option.New(identContext{}, v)}
|
||||
}
|
||||
|
||||
// WithFS specifies the source `fs.FS` object to read the file from.
|
||||
func WithFS(v fs.FS) ReadFileOption {
|
||||
return &readFileOption{option.New(identFS{}, v)}
|
||||
}
|
||||
|
||||
func WithKeyProvider(v KeyProvider) DecryptOption {
|
||||
return &decryptOption{option.New(identKeyProvider{}, v)}
|
||||
}
|
||||
|
||||
// WithKeyUsed allows you to specify the `jwe.Decrypt()` function to
|
||||
// return the key used for decryption. This may be useful when
|
||||
// you specify multiple key sources or if you pass a `jwk.Set`
|
||||
// and you want to know which key was successful at decrypting the
|
||||
// CEK.
|
||||
//
|
||||
// `v` must be a pointer to an empty `any`. Do not use
|
||||
// `jwk.Key` here unless you are 100% sure that all keys that you
|
||||
// have provided are instances of `jwk.Key` (remember that the
|
||||
// jwx API allows users to specify a raw key such as *rsa.PublicKey)
|
||||
func WithKeyUsed(v any) DecryptOption {
|
||||
return &decryptOption{option.New(identKeyUsed{}, v)}
|
||||
}
|
||||
|
||||
// WithLegacyHeaderMerging specifies whether to perform legacy header merging
|
||||
// when encrypting a JWE message in JSON serialization, when there is a single recipient.
|
||||
// This behavior is enabled by default for backwards compatibility.
|
||||
//
|
||||
// When a JWE message is encrypted in JSON serialization, and there is only
|
||||
// one recipient, this library automatically serializes the message in
|
||||
// flattened JSON serialization format. In older versions of this library,
|
||||
// the protected headers and the per-recipient headers were merged together
|
||||
// before computing the AAD (Additional Authenticated Data), but the per-recipient
|
||||
// headers were kept as-is in the `header` field of the recipient object.
|
||||
//
|
||||
// This behavior is not compliant with the JWE specification, which states that
|
||||
// the headers must be disjoint.
|
||||
//
|
||||
// Passing this option with a value of `false` disables this legacy behavior,
|
||||
// and while the per-recipient headers and protected headers are still merged
|
||||
// for the purpose of computing AAD, the per-recipient headers are cleared
|
||||
// after merging, so that the resulting JWE message is compliant with the
|
||||
// specification.
|
||||
//
|
||||
// This option has no effect when there are multiple recipients, or when
|
||||
// the serialization format is compact serialization. For multiple recipients
|
||||
// (i.e. full JSON serialization), the protected headers and per-recipient
|
||||
// headers are never merged, and it is the caller's responsibility to ensure
|
||||
// that the headers are disjoint. In compact serialization, there are no per-recipient
|
||||
// headers; in fact, the protected headers are the only headers that exist,
|
||||
// and therefore there is no possibility of header collision after merging
|
||||
// (note: while per-recipient headers do not make sense in compact serialization,
|
||||
// this library does not prevent you from setting them -- they are all just
|
||||
// merged into the protected headers).
|
||||
//
|
||||
// In future versions, the new behavior will be the default. New users are
|
||||
// encouraged to set this option to `false` now to avoid future issues.
|
||||
func WithLegacyHeaderMerging(v bool) EncryptOption {
|
||||
return &encryptOption{option.New(identLegacyHeaderMerging{}, v)}
|
||||
}
|
||||
|
||||
// WithMaxDecompressBufferSize specifies the maximum buffer size for used when
|
||||
// decompressing the payload of a JWE message. If a compressed JWE payload
|
||||
// exceeds this amount when decompressed, jwe.Decrypt will return an error.
|
||||
// The default value is 10MB.
|
||||
//
|
||||
// This option can be used for `jwe.Settings()`, which changes the behavior
|
||||
// globally, or for `jwe.Decrypt()`, which changes the behavior for that
|
||||
// specific call.
|
||||
func WithMaxDecompressBufferSize(v int64) GlobalDecryptOption {
|
||||
return &globalDecryptOption{option.New(identMaxDecompressBufferSize{}, v)}
|
||||
}
|
||||
|
||||
// WithMaxPBES2Count specifies the maximum number of PBES2 iterations
|
||||
// to use when decrypting a message. If not specified, the default
|
||||
// value of 10,000 is used.
|
||||
//
|
||||
// This option has a global effect.
|
||||
func WithMaxPBES2Count(v int) GlobalOption {
|
||||
return &globalOption{option.New(identMaxPBES2Count{}, v)}
|
||||
}
|
||||
|
||||
// WithMergeProtectedHeaders specify that when given multiple headers
|
||||
// as options to `jwe.Encrypt`, these headers should be merged instead
|
||||
// of overwritten
|
||||
func WithMergeProtectedHeaders(v bool) EncryptOption {
|
||||
return &encryptOption{option.New(identMergeProtectedHeaders{}, v)}
|
||||
}
|
||||
|
||||
// WithMessage provides a message object to be populated by `jwe.Decrypt`
|
||||
// Using this option allows you to decrypt AND obtain the `jwe.Message`
|
||||
// in one go.
|
||||
func WithMessage(v *Message) DecryptOption {
|
||||
return &decryptOption{option.New(identMessage{}, v)}
|
||||
}
|
||||
|
||||
// WithPretty specifies whether the JSON output should be formatted and
|
||||
// indented
|
||||
func WithPretty(v bool) WithJSONSuboption {
|
||||
return &withJSONSuboption{option.New(identPretty{}, v)}
|
||||
}
|
||||
|
||||
// WithRequiredKid specifies whether the keys in the jwk.Set should
|
||||
// only be matched if the target JWE message's Key ID and the Key ID
|
||||
// in the given key matches.
|
||||
func WithRequireKid(v bool) WithKeySetSuboption {
|
||||
return &withKeySetSuboption{option.New(identRequireKid{}, v)}
|
||||
}
|
||||
|
||||
// WithCompact specifies that the result of `jwe.Encrypt()` is serialized in
|
||||
// compact format.
|
||||
//
|
||||
// By default `jwe.Encrypt()` will opt to use compact format, so you usually
|
||||
// do not need to specify this option other than to be explicit about it
|
||||
func WithCompact() EncryptOption {
|
||||
return &encryptOption{option.New(identSerialization{}, fmtCompact)}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
load("@rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "jwk",
|
||||
srcs = [
|
||||
"cache.go",
|
||||
"convert.go",
|
||||
"ecdsa.go",
|
||||
"ecdsa_gen.go",
|
||||
"errors.go",
|
||||
"fetch.go",
|
||||
"filter.go",
|
||||
"interface.go",
|
||||
"interface_gen.go",
|
||||
"io.go",
|
||||
"jwk.go",
|
||||
"key_ops.go",
|
||||
"okp.go",
|
||||
"okp_gen.go",
|
||||
"options.go",
|
||||
"options_gen.go",
|
||||
"parser.go",
|
||||
"rsa.go",
|
||||
"rsa_gen.go",
|
||||
"set.go",
|
||||
"symmetric.go",
|
||||
"symmetric_gen.go",
|
||||
"usage.go",
|
||||
"whitelist.go",
|
||||
"x509.go",
|
||||
],
|
||||
importpath = "github.com/lestrrat-go/jwx/v3/jwk",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cert",
|
||||
"//internal/base64",
|
||||
"//internal/ecutil",
|
||||
"//transform",
|
||||
"//internal/json",
|
||||
"//internal/pool",
|
||||
"//internal/tokens",
|
||||
"//jwa",
|
||||
"//jwk/ecdsa",
|
||||
"//jwk/jwkbb",
|
||||
"@com_github_lestrrat_go_blackmagic//:blackmagic",
|
||||
"@com_github_lestrrat_go_httprc_v3//:httprc",
|
||||
"@com_github_lestrrat_go_option_v2//:option",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "jwk_test",
|
||||
srcs = [
|
||||
"filter_test.go",
|
||||
"headers_test.go",
|
||||
"jwk_internal_test.go",
|
||||
"jwk_test.go",
|
||||
"options_gen_test.go",
|
||||
"refresh_test.go",
|
||||
"set_test.go",
|
||||
"x5c_test.go",
|
||||
],
|
||||
data = glob(["testdata/**"]),
|
||||
embed = [":jwk"],
|
||||
deps = [
|
||||
"//cert",
|
||||
"//internal/base64",
|
||||
"//internal/jose",
|
||||
"//internal/json",
|
||||
"//internal/jwxtest",
|
||||
"//internal/tokens",
|
||||
"//jwa",
|
||||
"//jwk/ecdsa",
|
||||
"//jws",
|
||||
"@com_github_lestrrat_go_blackmagic//:blackmagic",
|
||||
"@com_github_lestrrat_go_httprc_v3//:httprc",
|
||||
"@com_github_lestrrat_go_httprc_v3//tracesink",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
||||
alias(
|
||||
name = "go_default_library",
|
||||
actual = ":jwk",
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
# JWK [](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk)
|
||||
|
||||
Package jwk implements JWK as described in [RFC7517](https://tools.ietf.org/html/rfc7517).
|
||||
If you are looking to use JWT wit JWKs, look no further than [github.com/lestrrat-go/jwx](../jwt).
|
||||
|
||||
* Parse and work with RSA/EC/Symmetric/OKP JWK types
|
||||
* Convert to and from JSON
|
||||
* Convert to and from raw key types (e.g. *rsa.PrivateKey)
|
||||
* Ability to keep a JWKS fresh using *jwk.AutoRefresh
|
||||
|
||||
## Supported key types:
|
||||
|
||||
| kty | Curve | Go Key Type |
|
||||
|:----|:------------------------|:----------------------------------------------|
|
||||
| RSA | N/A | rsa.PrivateKey / rsa.PublicKey (2) |
|
||||
| EC | P-256<br>P-384<br>P-521<br>secp256k1 (1) | ecdsa.PrivateKey / ecdsa.PublicKey (2) |
|
||||
| oct | N/A | []byte |
|
||||
| OKP | Ed25519 (1) | ed25519.PrivateKey / ed25519.PublicKey (2) |
|
||||
| | X25519 (1) | (jwx/)x25519.PrivateKey / x25519.PublicKey (2)|
|
||||
|
||||
* Note 1: Experimental
|
||||
* Note 2: Either value or pointers accepted (e.g. rsa.PrivateKey or *rsa.PrivateKey)
|
||||
|
||||
# Documentation
|
||||
|
||||
Please read the [API reference](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3/jwk), or
|
||||
the how-to style documentation on how to use JWK can be found in the [docs directory](../docs/04-jwk.md).
|
||||
|
||||
# Auto-Refresh a key during a long-running process
|
||||
|
||||
<!-- INCLUDE(examples/jwk_cache_example_test.go) -->
|
||||
```go
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/lestrrat-go/httprc/v3"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
func Example_jwk_cache() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
const googleCerts = `https://www.googleapis.com/oauth2/v3/certs`
|
||||
|
||||
// First, set up the `jwk.Cache` object. You need to pass it a
|
||||
// `context.Context` object to control the lifecycle of the background fetching goroutine.
|
||||
c, err := jwk.NewCache(ctx, httprc.NewClient())
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create cache: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Tell *jwk.Cache that we only want to refresh this JWKS periodically.
|
||||
if err := c.Register(ctx, googleCerts); err != nil {
|
||||
fmt.Printf("failed to register google JWKS: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Pretend that this is your program's main loop
|
||||
MAIN:
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
break MAIN
|
||||
default:
|
||||
}
|
||||
keyset, err := c.Lookup(ctx, googleCerts)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to fetch google JWKS: %s\n", err)
|
||||
return
|
||||
}
|
||||
_ = keyset
|
||||
// The returned `keyset` will always be "reasonably" new.
|
||||
//
|
||||
// By "reasonably" we mean that we cannot guarantee that the keys will be refreshed
|
||||
// immediately after it has been rotated in the remote source. But it should be close\
|
||||
// enough, and should you need to forcefully refresh the token using the `(jwk.Cache).Refresh()` method.
|
||||
//
|
||||
// If refetching the keyset fails, a cached version will be returned from the previous
|
||||
// successful sync
|
||||
|
||||
// Do interesting stuff with the keyset... but here, we just
|
||||
// sleep for a bit
|
||||
time.Sleep(time.Second)
|
||||
|
||||
// Because we're a dummy program, we just cancel the loop now.
|
||||
// If this were a real program, you presumably loop forever
|
||||
cancel()
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
```
|
||||
source: [examples/jwk_cache_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_cache_example_test.go)
|
||||
<!-- END INCLUDE -->
|
||||
|
||||
Parse and use a JWK key:
|
||||
|
||||
<!-- INCLUDE(examples/jwk_example_test.go) -->
|
||||
```go
|
||||
package examples_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/lestrrat-go/jwx/v3/internal/json"
|
||||
"github.com/lestrrat-go/jwx/v3/jwk"
|
||||
)
|
||||
|
||||
func Example_jwk_usage() {
|
||||
// Use jwk.Cache if you intend to keep reuse the JWKS over and over
|
||||
set, err := jwk.Fetch(context.Background(), "https://www.googleapis.com/oauth2/v3/certs")
|
||||
if err != nil {
|
||||
log.Printf("failed to parse JWK: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Key sets can be serialized back to JSON
|
||||
{
|
||||
jsonbuf, err := json.Marshal(set)
|
||||
if err != nil {
|
||||
log.Printf("failed to marshal key set into JSON: %s", err)
|
||||
return
|
||||
}
|
||||
log.Printf("%s", jsonbuf)
|
||||
}
|
||||
|
||||
for i := 0; i < set.Len(); i++ {
|
||||
var rawkey any // This is where we would like to store the raw key, like *rsa.PrivateKey or *ecdsa.PrivateKey
|
||||
key, ok := set.Key(i) // This retrieves the corresponding jwk.Key
|
||||
if !ok {
|
||||
log.Printf("failed to get key at index %d", i)
|
||||
return
|
||||
}
|
||||
|
||||
// jws and jwe operations can be performed using jwk.Key, but you could also
|
||||
// covert it to their "raw" forms, such as *rsa.PrivateKey or *ecdsa.PrivateKey
|
||||
if err := jwk.Export(key, &rawkey); err != nil {
|
||||
log.Printf("failed to create public key: %s", err)
|
||||
return
|
||||
}
|
||||
_ = rawkey
|
||||
|
||||
// You can create jwk.Key from a raw key, too
|
||||
fromRawKey, err := jwk.Import(rawkey)
|
||||
if err != nil {
|
||||
log.Printf("failed to acquire raw key from jwk.Key: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Keys can be serialized back to JSON
|
||||
jsonbuf, err := json.Marshal(key)
|
||||
if err != nil {
|
||||
log.Printf("failed to marshal key into JSON: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fromJSONKey, err := jwk.Parse(jsonbuf)
|
||||
if err != nil {
|
||||
log.Printf("failed to parse json: %s", err)
|
||||
return
|
||||
}
|
||||
_ = fromJSONKey
|
||||
_ = fromRawKey
|
||||
}
|
||||
// OUTPUT:
|
||||
}
|
||||
|
||||
//nolint:govet
|
||||
func Example_jwk_marshal_json() {
|
||||
// JWKs that inherently involve randomness such as RSA and EC keys are
|
||||
// not used in this example, because they may produce different results
|
||||
// depending on the environment.
|
||||
//
|
||||
// (In fact, even if you use a static source of randomness, tests may fail
|
||||
// because of internal changes in the Go runtime).
|
||||
|
||||
raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF")
|
||||
|
||||
// This would create a symmetric key
|
||||
key, err := jwk.Import(raw)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to create symmetric key: %s\n", err)
|
||||
return
|
||||
}
|
||||
if _, ok := key.(jwk.SymmetricKey); !ok {
|
||||
fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
|
||||
return
|
||||
}
|
||||
|
||||
key.Set(jwk.KeyIDKey, "mykey")
|
||||
|
||||
buf, err := json.MarshalIndent(key, "", " ")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to marshal key into JSON: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", buf)
|
||||
|
||||
// OUTPUT:
|
||||
// {
|
||||
// "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY",
|
||||
// "kid": "mykey",
|
||||
// "kty": "oct"
|
||||
// }
|
||||
}
|
||||
```
|
||||
source: [examples/jwk_example_test.go](https://github.com/lestrrat-go/jwx/blob/v3/examples/jwk_example_test.go)
|
||||
<!-- END INCLUDE -->
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
package jwk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/lestrrat-go/httprc/v3"
|
||||
)
|
||||
|
||||
type HTTPClient = httprc.HTTPClient
|
||||
type ErrorSink = httprc.ErrorSink
|
||||
type TraceSink = httprc.TraceSink
|
||||
|
||||
// Cache is a container built on top of github.com/lestrrat-go/httprc/v3
|
||||
// that keeps track of Set object by their source URLs.
|
||||
// The Set objects are stored in memory, and are refreshed automatically
|
||||
// behind the scenes.
|
||||
//
|
||||
// Before retrieving the Set objects, the user must pre-register the
|
||||
// URLs they intend to use by calling `Register()`
|
||||
//
|
||||
// c := jwk.NewCache(ctx, httprc.NewClient())
|
||||
// c.Register(ctx, url, options...)
|
||||
//
|
||||
// Once registered, you can call `Get()` to retrieve the Set object.
|
||||
//
|
||||
// All JWKS objects that are retrieved via this mechanism should be
|
||||
// treated read-only, as they are shared among all consumers, as well
|
||||
// as the `jwk.Cache` object.
|
||||
//
|
||||
// There are cases where `jwk.Cache` and `jwk.CachedSet` should and
|
||||
// should not be used.
|
||||
//
|
||||
// First and foremost, do NOT use a cache for those JWKS objects that
|
||||
// need constant checking. For example, unreliable or user-provided JWKS (i.e. those
|
||||
// JWKS that are not from a well-known provider) should not be fetched
|
||||
// through a `jwk.Cache` or `jwk.CachedSet`.
|
||||
//
|
||||
// For example, if you have a flaky JWKS server for development
|
||||
// that can go down often, you should consider alternatives such as
|
||||
// providing `http.Client` with a caching `http.RoundTripper` configured
|
||||
// (see `jwk.WithHTTPClient`), setting up a reverse proxy, etc.
|
||||
// These techniques allow you to set up a more robust way to both cache
|
||||
// and report precise causes of the problems than using `jwk.Cache` or
|
||||
// `jwk.CachedSet`. If you handle the caching at the HTTP level like this,
|
||||
// you will be able to use a simple `jwk.Fetch` call and not worry about the cache.
|
||||
//
|
||||
// User-provided JWKS objects may also be problematic, as it may go down
|
||||
// unexpectedly (and frequently!), and it will be hard to detect when
|
||||
// the URLs or its contents are swapped.
|
||||
//
|
||||
// A good use-case for `jwk.Cache` and `jwk.CachedSet` are for "stable"
|
||||
// JWKS objects.
|
||||
//
|
||||
// When we say "stable", we are thinking of JWKS that should mostly be
|
||||
// ALWAYS available. A good example are those JWKS objects provided by
|
||||
// major cloud providers such as Google Cloud, AWS, or Azure.
|
||||
// Stable JWKS may still experience intermittent network connectivity problems,
|
||||
// but you can expect that they will eventually recover in relatively
|
||||
// short period of time. They rarely change URLs, and the contents are
|
||||
// expected to be valid or otherwise it would cause havoc to those providers
|
||||
//
|
||||
// We also know that these stable JWKS objects are rotated periodically,
|
||||
// which is a perfect use for `jwk.Cache` and `jwk.CachedSet`. The caches
|
||||
// can be configured to periodically refresh the JWKS thereby keeping them
|
||||
// fresh without extra intervention from the developer.
|
||||
//
|
||||
// Notice that for these recommended use-cases the requirement to check
|
||||
// the validity or the availability of the JWKS objects are non-existent,
|
||||
// as it is expected that they will be available and will be valid. The
|
||||
// caching mechanism can hide intermittent connectivity problems as well
|
||||
// as keep the objects mostly fresh.
|
||||
type Cache struct {
|
||||
ctrl httprc.Controller
|
||||
}
|
||||
|
||||
// Transformer is a specialized version of `httprc.Transformer` that implements
|
||||
// conversion from a `http.Response` object to a `jwk.Set` object. Use this in
|
||||
// conjection with `httprc.NewResource` to create a `httprc.Resource` object
|
||||
// to auto-update `jwk.Set` objects.
|
||||
type Transformer struct {
|
||||
parseOptions []ParseOption
|
||||
}
|
||||
|
||||
func (t Transformer) Transform(_ context.Context, res *http.Response) (Set, error) {
|
||||
buf, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to read response body status: %w`, err)
|
||||
}
|
||||
|
||||
set, err := Parse(buf, t.parseOptions...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to parse JWK set at %q: %w`, res.Request.URL.String(), err)
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// NewCache creates a new `jwk.Cache` object.
|
||||
//
|
||||
// Under the hood, `jwk.Cache` uses `httprc.Client` manage the
|
||||
// fetching and caching of JWKS objects, and thus spawns multiple goroutines
|
||||
// per `jwk.Cache` object.
|
||||
//
|
||||
// The provided `httprc.Client` object must NOT be started prior to
|
||||
// passing it to `jwk.NewCache`. The `jwk.Cache` object will start
|
||||
// the `httprc.Client` object on its own.
|
||||
func NewCache(ctx context.Context, client *httprc.Client) (*Cache, error) {
|
||||
ctrl, err := client.Start(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to start httprc.Client: %w`, err)
|
||||
}
|
||||
|
||||
return &Cache{
|
||||
ctrl: ctrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Register registers a URL to be managed by the cache. URLs must
|
||||
// be registered before issuing `Get`
|
||||
//
|
||||
// The `Register` method is a thin wrapper around `(httprc.Controller).Add`
|
||||
func (c *Cache) Register(ctx context.Context, u string, options ...RegisterOption) error {
|
||||
var parseOptions []ParseOption
|
||||
var resourceOptions []httprc.NewResourceOption
|
||||
waitReady := true
|
||||
for _, option := range options {
|
||||
switch option := option.(type) {
|
||||
case ParseOption:
|
||||
parseOptions = append(parseOptions, option)
|
||||
case ResourceOption:
|
||||
var v httprc.NewResourceOption
|
||||
if err := option.Value(&v); err != nil {
|
||||
return fmt.Errorf(`failed to retrieve NewResourceOption option value: %w`, err)
|
||||
}
|
||||
resourceOptions = append(resourceOptions, v)
|
||||
default:
|
||||
switch option.Ident() {
|
||||
case identHTTPClient{}:
|
||||
var cli HTTPClient
|
||||
if err := option.Value(&cli); err != nil {
|
||||
return fmt.Errorf(`failed to retrieve HTTPClient option value: %w`, err)
|
||||
}
|
||||
resourceOptions = append(resourceOptions, httprc.WithHTTPClient(cli))
|
||||
case identWaitReady{}:
|
||||
if err := option.Value(&waitReady); err != nil {
|
||||
return fmt.Errorf(`failed to retrieve WaitReady option value: %w`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r, err := httprc.NewResource[Set](u, &Transformer{
|
||||
parseOptions: parseOptions,
|
||||
}, resourceOptions...)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`failed to create httprc.Resource: %w`, err)
|
||||
}
|
||||
if err := c.ctrl.Add(ctx, r, httprc.WithWaitReady(waitReady)); err != nil {
|
||||
return fmt.Errorf(`failed to add resource to httprc.Client: %w`, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookupResource returns the `httprc.Resource` object associated with the
|
||||
// given URL `u`. If the URL has not been registered, an error is returned.
|
||||
func (c *Cache) LookupResource(ctx context.Context, u string) (*httprc.ResourceBase[Set], error) {
|
||||
r, err := c.ctrl.Lookup(ctx, u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
|
||||
}
|
||||
//nolint:forcetypeassert
|
||||
return r.(*httprc.ResourceBase[Set]), nil
|
||||
}
|
||||
|
||||
func (c *Cache) Lookup(ctx context.Context, u string) (Set, error) {
|
||||
r, err := c.LookupResource(ctx, u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
|
||||
}
|
||||
set := r.Resource()
|
||||
if set == nil {
|
||||
return nil, fmt.Errorf(`resource %q is not ready`, u)
|
||||
}
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Ready(ctx context.Context, u string) bool {
|
||||
r, err := c.LookupResource(ctx, u)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if err := r.Ready(ctx); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Refresh is identical to Get(), except it always fetches the
|
||||
// specified resource anew, and updates the cached content
|
||||
//
|
||||
// Please refer to the documentation for `(httprc.Cache).Refresh` for
|
||||
// more details
|
||||
func (c *Cache) Refresh(ctx context.Context, u string) (Set, error) {
|
||||
if err := c.ctrl.Refresh(ctx, u); err != nil {
|
||||
return nil, fmt.Errorf(`failed to refresh resource %q: %w`, u, err)
|
||||
}
|
||||
return c.Lookup(ctx, u)
|
||||
}
|
||||
|
||||
// IsRegistered returns true if the given URL `u` has already been registered
|
||||
// in the cache.
|
||||
func (c *Cache) IsRegistered(ctx context.Context, u string) bool {
|
||||
_, err := c.LookupResource(ctx, u)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Unregister removes the given URL `u` from the cache.
|
||||
func (c *Cache) Unregister(ctx context.Context, u string) error {
|
||||
return c.ctrl.Remove(ctx, u)
|
||||
}
|
||||
|
||||
func (c *Cache) Shutdown(ctx context.Context) error {
|
||||
return c.ctrl.ShutdownContext(ctx)
|
||||
}
|
||||
|
||||
// CachedSet is a thin shim over jwk.Cache that allows the user to cloak
|
||||
// jwk.Cache as if it's a `jwk.Set`. Behind the scenes, the `jwk.Set` is
|
||||
// retrieved from the `jwk.Cache` for every operation.
|
||||
//
|
||||
// Since `jwk.CachedSet` always deals with a cached version of the `jwk.Set`,
|
||||
// all operations that mutate the object (such as AddKey(), RemoveKey(), et. al)
|
||||
// are no-ops and return an error.
|
||||
//
|
||||
// Note that since this is a utility shim over `jwk.Cache`, you _will_ lose
|
||||
// the ability to control the finer details (such as controlling how long to
|
||||
// wait for in case of a fetch failure using `context.Context`)
|
||||
//
|
||||
// Make sure that you read the documentation for `jwk.Cache` as well.
|
||||
type CachedSet interface {
|
||||
Set
|
||||
cached() (Set, error) // used as a marker
|
||||
}
|
||||
|
||||
type cachedSet struct {
|
||||
r *httprc.ResourceBase[Set]
|
||||
}
|
||||
|
||||
func (c *Cache) CachedSet(u string) (CachedSet, error) {
|
||||
r, err := c.LookupResource(context.Background(), u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to lookup resource %q: %w`, u, err)
|
||||
}
|
||||
return NewCachedSet(r), nil
|
||||
}
|
||||
|
||||
func NewCachedSet(r *httprc.ResourceBase[Set]) CachedSet {
|
||||
return &cachedSet{
|
||||
r: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *cachedSet) cached() (Set, error) {
|
||||
if err := cs.r.Ready(context.Background()); err != nil {
|
||||
return nil, fmt.Errorf(`failed to fetch resource: %w`, err)
|
||||
}
|
||||
return cs.r.Resource(), nil
|
||||
}
|
||||
|
||||
// AddKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
|
||||
func (*cachedSet) AddKey(_ Key) error {
|
||||
return fmt.Errorf(`(jwk.Cachedset).AddKey: jwk.CachedSet is immutable`)
|
||||
}
|
||||
|
||||
// Clear is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
|
||||
func (*cachedSet) Clear() error {
|
||||
return fmt.Errorf(`(jwk.cachedSet).Clear: jwk.CachedSet is immutable`)
|
||||
}
|
||||
|
||||
// Set is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
|
||||
func (*cachedSet) Set(_ string, _ any) error {
|
||||
return fmt.Errorf(`(jwk.cachedSet).Set: jwk.CachedSet is immutable`)
|
||||
}
|
||||
|
||||
// Remove is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
|
||||
func (*cachedSet) Remove(_ string) error {
|
||||
// TODO: Remove() should be renamed to Remove(string) error
|
||||
return fmt.Errorf(`(jwk.cachedSet).Remove: jwk.CachedSet is immutable`)
|
||||
}
|
||||
|
||||
// RemoveKey is a no-op for `jwk.CachedSet`, as the `jwk.Set` should be treated read-only
|
||||
func (*cachedSet) RemoveKey(_ Key) error {
|
||||
return fmt.Errorf(`(jwk.cachedSet).RemoveKey: jwk.CachedSet is immutable`)
|
||||
}
|
||||
|
||||
func (cs *cachedSet) Clone() (Set, error) {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`failed to get cached jwk.Set: %w`, err)
|
||||
}
|
||||
|
||||
return set.Clone()
|
||||
}
|
||||
|
||||
// Get returns the value of non-Key field stored in the jwk.Set
|
||||
func (cs *cachedSet) Get(name string, dst any) error {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return set.Get(name, dst)
|
||||
}
|
||||
|
||||
// Key returns the Key at the specified index
|
||||
func (cs *cachedSet) Key(idx int) (Key, bool) {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return set.Key(idx)
|
||||
}
|
||||
|
||||
func (cs *cachedSet) Index(key Key) int {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return set.Index(key)
|
||||
}
|
||||
|
||||
func (cs *cachedSet) Keys() []string {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return set.Keys()
|
||||
}
|
||||
|
||||
func (cs *cachedSet) Len() int {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
||||
return set.Len()
|
||||
}
|
||||
|
||||
func (cs *cachedSet) LookupKeyID(kid string) (Key, bool) {
|
||||
set, err := cs.cached()
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return set.LookupKeyID(kid)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user