Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs #1000

Merged
merged 3 commits into from
Oct 25, 2023
Merged

Docs #1000

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Changes-v3.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ These are changes that are incompatible with the v2.x.x version.
`Get(string, interface{}) error`, where the second argument should be a pointer
to the storage destination of the field.

* Iterator related methods such as, `Iterate()`, `AsMAp()`, `Walk()`, `Visit()`, etc have all been
removed. Use `Keys()` to iterate through elements. Also, methods such as `Copy()` and `Merge()`
no longer takes `context.Context` as its first argument.

* Method `Has()` to query the presence of a field has been added

* Added ability to define new `jwk.Key` types.

* `PrivateParams()` has been removed.

## JWS

* Iterators have been completely removed.
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# github.com/lestrrat-go/jwx/v2 ![](https://github.com/lestrrat-go/jwx/workflows/CI/badge.svg?branch=v2) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v2.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2) [![codecov.io](https://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=v2)](https://codecov.io/github/lestrrat-go/jwx?branch=v2)
# github.com/lestrrat-go/jwx/v3 ![](https://github.com/lestrrat-go/jwx/workflows/CI/badge.svg?branch=v3) [![Go Reference](https://pkg.go.dev/badge/github.com/lestrrat-go/jwx/v3.svg)](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3) [![codecov.io](https://codecov.io/github/lestrrat-go/jwx/coverage.svg?branch=v3)](https://codecov.io/github/lestrrat-go/jwx?branch=v3)

Go module implementing various JWx (JWA/JWE/JWK/JWS/JWT, otherwise known as JOSE) technologies.

Expand All @@ -23,7 +23,7 @@ If you are using this module in your product or your company, please add your p
Some more in-depth discussion on why you might want to use this library over others
can be found in the [Description section](#description)

If you are using v0 or v1, you are strongly encouraged to migrate to using v2
If you are using v0 or v1, you are strongly encouraged to migrate to the latest
(the version that comes with the README you are reading).

# SYNOPSIS
Expand Down Expand Up @@ -149,7 +149,7 @@ source: [examples/jwx_readme_example_test.go](https://github.com/lestrrat-go/jwx

# How-to Documentation

* [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v2)
* [API documentation](https://pkg.go.dev/github.com/lestrrat-go/jwx/v3)
* [How-to style documentation](./docs)
* [Runnable Examples](./examples)

Expand Down Expand Up @@ -177,7 +177,7 @@ For example, a certain library looks like it had most of JWS, JWE, JWK covered,

Because I was writing the server side (and the client side for testing), I needed the *entire* JOSE toolset to properly implement my server, **and** they needed to be *flexible* enough to fulfill the entire spec that I was writing.

So here's `github.com/lestrrat-go/jwx/v2`. This library is extensible, customizable, and hopefully well organized to the point that it is easy for you to slice and dice it.
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?

Expand Down
107 changes: 0 additions & 107 deletions jwk/jwk.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,113 +47,6 @@ func init() {
}
}

// # Registering a key type
//
// You can add the ability to use a JWK that this library does not
// implement out of the box. You can do this by registering your own
// KeyParser instance.
//
// func init() {
// // optional
// jwk.RegiserProbeField(reflect.StructField{Name: "SomeHint", Type: reflect.TypeOf(""), Tag: `json:"some_hint"`})
// jwk.RegisterKeyParser(&MyKeyParser{})
// }
//
// In order to understand how this works, you need to understand
// how the `jwk.ParseKey()` works.
//
// The first thing that occurs when parsing a key is a partial
// unmarshaling of the payload into a hint / probe object.
//
// Because the `json.Unmarshal` works by calling the `UnmarshalJSON`
// method on a concrete object, we need to create one first. In order
// to create the appropriate Go object, we need to peek into the
// payload and figure out what type of key it is.
//
// In order to do this, we create a new KeyProber to partially populate
// the object with hints from the payload. For example, a JWK representing
// an RSA key would look like:
//
// { "kty": "RSA", "n": ..., "e": ..., ... }
//
// Therefore, a KeyProbe that can unmarshal the value of the field "kty"
// would be able to tell us that this is an RSA key.
//
// Also, if said payload contains some value in the "d" field, we can
// also tell that this is a private key, as only private keys need
// this field.
//
// For most cases, the default KeyProbe implementation should be sufficient.
// You would be able to query "kty" and "d" fields via the `Get()` method.
//
// var kty string
// _ = probe.Get("Kty", &kty)
//
// However, if you need extra pieces of information, you can specify
// additional fields to be probed. For example, if you want to know the
// value of the field "my_hint" (which holds a string value) from the payload,
// you can register it to be probed by registering an additional probe field like this:
//
// jwk.RegisterProbeField(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
//
// Once the probe is done, the library will iterate over the registered parsers
// and attempt to parse the key by calling their `ParseKey()` methods.
// The parsers will be called in reverse order that they were registered.
// This means that it will try all parsers that were registered by third
// parties, and once those are exhausted, the default parser will be used.
//
// Each parser's `ParseKey()`` method will receive three arguments: the probe object, a
// KeyUnmarshaler, and the raw payload. The probe object can be used
// as a hint to determine what kind of key to instantiate. An example
// pseudocode may look like this:
//
// var kty string
// _ = probe.Get("Kty", &kty)
// switch kty {
// case "RSA":
// // create an RSA key
// case "EC":
// // create an EC key
// ...
// }
//
// The `KeyUnmarshaler` is a thin wrapper around `json.Unmarshal` it
// works almost identical to `json.Unmarshal`, but it allows us to
// add extra magic that is specific to this library before calling
// the actual `json.Unmarshal`. If you want to try to unmarshal the
// payload, please use this instead of `json.Unmarshal`.
//
// func init() {
// jwk.RegisterFieldProbe(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
// jwk.RegisterParser(&MyKeyParser{})
// }
//
// type MyKeyParser struct { ... }
// func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) {
// // Create concrete type
// var hint string
// if err := probe.Get("MyHint", &hint); err != nil {
// // if it doesn't have the `my_hint` field, it probably means
// // it's not for us, so we return ContinueParseError so that
// // the next parser can pick it up
// return nil, jwk.ContinueParseError()
// }
//
// // Use hint to determine concrete key type
// var key jwk.Key
// switch hint {
// case ...:
// key = = myNewAwesomeJWK()
// ...
//
// return unmarshaler.Unmarshal(data, key)
// }
//
// This functionality should be considered experimental. While we
// expect that the functionality itself will remain, the API may
// change in backward incompatible ways, even during minor version
// releases.

var cpe = &continueError{}

// ContinueError returns an opaque error that can be returned
Expand Down
139 changes: 139 additions & 0 deletions jwk/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,145 @@ var keyParsers = []KeyParser{KeyParseFunc(defaultParseKey)}
// RegisterKeyParser adds a new KeyParser. Parsers are called in FILO order.
// That is, the last parser to be registered is called first. There is no
// check for duplicate entries.
//
// You can use a JWK that this library does not implement out of the box by
// registering a new `KeyParser` that can produce your custom JWK. For example,
// you may register a new parser and a key probe field like this:
//
// func init() {
// // optional
// jwk.RegiserProbeField(reflect.StructField{Name: "SomeHint", Type: reflect.TypeOf(""), Tag: `json:"some_hint"`})
// jwk.RegisterKeyParser(&MyKeyParser{})
// }
//
// In order to understand how this works, you need to understand
// how the `jwk.ParseKey()` works.
//
// The first thing that occurs when parsing a key is a partial
// unmarshaling of the payload into a hint / probe object.
// This must be done because when going from JSON to a concrete Go type,
// we must first create a concrete Go type _and then_ use `json.Unmarshal`
// to parse the JSON payload.
//
// key := NewMyKey()
// _ = json.Unmarshal(payload, key)
//
// But in order to create the concrete type, we need to know what kind of
// Go type we need. This can only be done by peeking into the payload.
// To do this, we use a struct that populates the minimal amount of
// information required to determine the concrete type.
//
// var probe ...
// _ = json.Unmarshal(payload, &probe)
// if probe.Hint == ... { /* pseudocode */
// concrete := newAwesomeToken() /* we now know the concrete type! */
// if err := json.Unmarshal(payload, concrete) { ... }
// }
//
// For the built-in types, we only need to know the value of the "kty"
// and "d" fields, to determine the key type (via "kty") and if it's
// a private or public key (via "d").
//
// For example, a JWK representing an RSA key would look like:
//
// { "kty": "RSA", "n": ..., "e": ..., ... }
//
// The KeyProbe partially unmarshals this object so that we can obtain the value
// of the field "kty". By inspecting the value of this field, we can determine
// that this is an RSA key. Following code shows the rough idea of how the
// key type is determined:
//
// var kty string
// _ = probe.Get("Kty", &kty)
// switch kty {
// case "RSA":
// // create an RSA key
// case "EC":
// // create an EC key
// ...
// }
//
// However this is not enough. We also need to know if this is a private
// or public key. In the case of an RSA key, the key is a private key if said payload contains
// some value in the "d" field.
//
// var key jwk.Key
// switch kty {
// case "RSA":
// var d json.RawMessage
// _ = probe.Get("D", &d)
// if len(d) > 0 {
// key = newRSAPrivateKey()
// } else {
// key = newRSAPublicKey()
// }
// ...
// }
//
// In this way we can finally unmarshal the payload into a concrete type.
//
// For most cases, the default KeyProbe implementation should be sufficient.
// However, in some cases you may need to query additional fields to determine
// to determine the concrete type.
// For example, if you want to know the value of the field "my_hint" (which holds a string value)
// from the payload, you can register it to be probed by registering an additional probe field like this:
//
// jwk.RegisterProbeField(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
//
// This will add a new field to the KeyProbe object dynamically, allowing it to
// capture the value of the "my_hint" field from the payload and store it in
// the "MyHint" slot of the probe.
//
// This probe will be passed to the registered parsers' `ParseKey()` methods.
// This is why the `KeyParser` interface contains the `*jwk.KeyProbe` object
// as its first argument. Following is how you would query the value of
// "MyHint" from the probe:
//
// func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) {
// var hint string
// if err := probe.Get("MyHint", &hint); err != nil { ... }
// ...
// }
//
// The second argument is a `KeyUnmarshaler` object. This is a thin wrapper
// `json.Unmarshal`. It works almost identical to `json.Unmarshal`, but
// adds extra magic that is specific to this library before calling
// the actual `json.Unmarshal`, but unfortunately you would need to know the
// internals of this library to fully use its magic. If you do not care
// about the details, just use the unmarshaler as you would `json.Unmarshal`.
//
// Combining this together, the following is how you would add a new `jwk.Key`
//
// func init() {
// jwk.RegisterFieldProbe(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
// jwk.RegisterParser(&MyKeyParser{})
// }
//
// type MyKeyParser struct { ... }
// func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) {
// // Create concrete type
// var hint string
// if err := probe.Get("MyHint", &hint); err != nil {
// // if it doesn't have the `my_hint` field, it probably means
// // it's not for us, so we return ContinueParseError so that
// // the next parser can pick it up
// return nil, jwk.ContinueParseError()
// }
//
// // Use hint to determine concrete key type
// var key jwk.Key
// switch hint {
// case ...:
// key = = myNewAwesomeJWK()
// ...
//
// return unmarshaler.Unmarshal(data, key)
// }
//
// This functionality should be considered experimental. While we
// expect that the functionality itself will remain, the API may
// change in backward incompatible ways, even during minor version
// releases.
func RegisterKeyParser(kp KeyParser) {
muKeyParser.Lock()
defer muKeyParser.Unlock()
Expand Down
Loading