Files
QSfera/Server/vendor/github.com/lestrrat-go/jwx/v3/jwk/doc.go
T
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

295 lines
12 KiB
Go

// Package jwk implements JWK as described in https://tools.ietf.org/html/rfc7517
//
// This package implements jwk.Key to represent a single JWK, and jwk.Set to represent
// a set of JWKs.
//
// The `jwk.Key` type is an interface, which hides the underlying implementation for
// each key type. Each key type can further be converted to interfaces for known
// types, such as `jwk.ECDSAPrivateKey`, `jwk.RSAPublicKey`, etc. This may not necessarily
// work for third party key types (see section on "Registering a key type" below).
//
// Users can create a JWK in two ways. One is to unmarshal a JSON representation of a
// key. The second one is to use `jwk.Import()` to import a raw key and convert it to
// a jwk.Key.
//
// # Simple Usage
//
// You can parse a JWK from a JSON payload:
//
// jwk.ParseKey([]byte(`{"kty":"EC",...}`))
//
// You can go back and forth between raw key types and JWKs:
//
// jwkKey, _ := jwk.Import(rsaPrivateKey)
// var rawKey *rsa.PRrivateKey
// jwkKey.Raw(&rawKey)
//
// You can use them to sign/verify/encrypt/decrypt:
//
// jws.Sign([]byte(`...`), jws.WithKey(jwa.RS256, jwkKey))
// jwe.Encrypt([]byte(`...`), jwe.WithKey(jwa.RSA_OAEP, jwkKey))
//
// See examples/jwk_parse_example_test.go and other files in the exmaples/ directory for more.
//
// # Advanced Usage: Registering a custom key type and conversion routines
//
// Caveat Emptor: Functionality around registering keys
// (KeyProbe/KeyParser/KeyImporter/KeyExporter) should be considered experimental.
// While we expect that the functionality itself will remain, the API may
// change in backward incompatible ways, even during minor version
// releases.
//
// ## tl;dr
//
// * KeyProbe: Used for parsing JWKs in JSON format. Probes hint fields to be used for later parsing by KeyParser
// * KeyParser: Used for parsing JWKs in JSON format. Parses the JSON payload into a jwk.Key using the KeyProbe as hint
// * KeyImporter: Used for converting raw key into jwk.Key.
// * KeyExporter: Used for converting jwk.Key into raw key.
//
// ## Overview
//
// You can add the ability to use a JWK type that this library does not
// implement out of the box. You can do this by registering your own
// KeyParser, KeyImporter, and KeyExporter instances.
//
// func init() {
// jwk.RegiserProbeField(reflect.StructField{Name: "SomeHint", Type: reflect.TypeOf(""), Tag: `json:"some_hint"`})
// jwk.RegisterKeyParser(&MyKeyParser{})
// jwk.RegisterKeyImporter(&MyKeyImporter{})
// jwk.RegisterKeyExporter(&MyKeyExporter{})
// }
//
// The KeyParser is used to parse JSON payloads and conver them into a jwk.Key.
// The KeyImporter is used to convert a raw key (e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc) into a jwk.Key.
// The KeyExporter is used to convert a jwk.Key into a raw key.
//
// Although we believe the mechanism has been streamline quite a lot, it is also true
// that the entire process of parsing and converting keys are much more convoluted than you might
// think. Please know before hand that if you intend to add support for a new key type,
// it _WILL_ require you to learn this module pretty much in-and-out.
//
// Read on for more explanation.
//
// ## Registering a KeyParser
//
// In order to understand how parsing works, we need to explain how the `jwk.ParseKey()` works.
//
// The first thing that occurs when parsing a key is a partial
// unmarshaling of the payload into a hint / probe object.
//
// Because the `json.Unmarshal` works by calling the `UnmarshalJSON`
// method on a concrete object, we need to create a concrete object first.
// In order/ to create the appropriate Go object, we need to know which concrete
// object to create from the JSON payload, meaning we need to peek into the
// payload and figure out what type of key it is.
//
// In order to do this, we effectively need to parse the JSON payload twice.
// First, we "probe" the payload to figure out what kind of key it is, then
// we parse it again to create the actual key object.
//
// For probing, we create a new "probe" object (KeyProbe, which is not
// directly available to end users) to populate the object with hints from the payload.
// For example, a JWK representing an RSA key would look like:
//
// { "kty": "RSA", "n": ..., "e": ..., ... }
//
// The default KeyProbe is constructed to unmarshal "kty" and "d" fields,
// because that is enough information to determine what kind of key to
// construct.
//
// For example, if the payload contains "kty" field with the value "RSA",
// we know that it's an RSA key. If it contains "EC", we know that it's
// an EC key. Furthermore, if the payload contains some value in the "d" field, we can
// also tell that this is a private key, as only private keys need
// this field.
//
// For most cases, the default KeyProbe implementation should be sufficient.
// However, there may be cases in the future where there are new key types
// that require further information. Perhaps you are embedding another hint
// in your JWK to further specify what kind of key it is. In that case, you
// would need to probe more.
//
// Normally you can only change how an object is unmarshaled by specifying
// JSON tags when defining a struct, but we use `reflect` package capabilities
// to create an object dynamically, which is shared among all parsing operations.
//
// To add a new field to be probed, you need to register a new `reflect.StructField`
// object that has all of the information. For example, the code below would
// register a field named "MyHint" that is of type string, and has a JSON tag
// of "my_hint".
//
// jwk.RegisterProbeField(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
//
// The value of this field can be retrieved by calling `Get()` method on the
// KeyProbe object (from the `KeyParser`'s `ParseKey()` method discussed later)
//
// var myhint string
// _ = probe.Get("MyHint", &myhint)
//
// var kty string
// _ = probe.Get("Kty", &kty)
//
// This mechanism allows you to be flexible when trying to determine the key type
// to instantiate.
//
// ## Parse via the KeyParser
//
// When `jwk.Parse` / `jwk.ParseKey` is called, the library will first probe
// the payload as discussed above.
//
// Once the probe is done, the library will iterate over the registered parsers
// and attempt to parse the key by calling their `ParseKey()` methods.
//
// The parsers will be called in reverse order that they were registered.
// This means that it will try all parsers that were registered by third
// parties, and once those are exhausted, the default parser will be used.
//
// Each parser's `ParseKey()“ method will receive three arguments: the probe object, a
// KeyUnmarshaler, and the raw payload. The probe object can be used
// as a hint to determine what kind of key to instantiate. An example
// pseudocode may look like this:
//
// var kty string
// _ = probe.Get("Kty", &kty)
// switch kty {
// case "RSA":
// // create an RSA key
// case "EC":
// // create an EC key
// ...
// }
//
// The `KeyUnmarshaler` is a thin wrapper around `json.Unmarshal`. It works almost
// identical to `json.Unmarshal`, but it allows us to add extra magic that is
// specific to this library (which users do not need to be aware of) before calling
// the actual `json.Unmarshal`. Please use the `KeyUnmarshaler` to unmarshal JWKs instead of `json.Unmarshal`.
//
// Putting it all together, the boiler plate for registering a new parser may look like this:
//
// func init() {
// jwk.RegisterFieldProbe(reflect.StructField{Name: "MyHint", Type: reflect.TypeOf(""), Tag: `json:"my_hint"`})
// jwk.RegisterParser(&MyKeyParser{})
// }
//
// type MyKeyParser struct { ... }
// func(*MyKeyParser) ParseKey(rawProbe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (jwk.Key, error) {
// // Create concrete type
// var hint string
// if err := probe.Get("MyHint", &hint); err != nil {
// // if it doesn't have the `my_hint` field, it probably means
// // it's not for us, so we return ContinueParseError so that
// // the next parser can pick it up
// return nil, jwk.ContinueParseError()
// }
//
// // Use hint to determine concrete key type
// var key jwk.Key
// switch hint {
// case ...:
// key = = myNewAwesomeJWK()
// ...
// }
//
// return unmarshaler.Unmarshal(data, key)
// }
//
// ## Registering KeyImporter/KeyExporter
//
// If you are going to do anything with the key that was parsed by your KeyParser,
// you will need to tell the library how to convert back and forth between
// raw keys and JWKs. Conversion from raw keys to jwk.Keys are done by KeyImporters,
// and conversion from jwk.Keys to raw keys are done by KeyExporters.
//
// ## Using jwk.Import() using KeyImporter
//
// Each KeyImporter is hooked to run against a specific raw key type.
//
// When `jwk.Import()` is called, the library will iterate over all registered
// KeyImporters for the specified raw key type, and attempt to convert the raw
// key to a JWK by calling the `Import()` method on each KeyImporter.
//
// The KeyImporter's `Import()` method will receive the raw key to be converted,
// and should return a JWK or an error if the conversion fails, or the return
// `jwk.ContinueError()` if the specified raw key cannot be handled by ths/ KeyImporter.
//
// Once a KeyImporter is available, you will be able to pass the raw key to `jwk.Import()`.
// The following example shows how you might register a KeyImporter for a hypotheical
// mypkg.SuperSecretKey:
//
// jwk.RegisterKeyImporter(&mypkg.SuperSecretKey{}, jwk.KeyImportFunc(imnportSuperSecretKey))
//
// func importSuperSecretKey(key any) (jwk.Key, error) {
// mykey, ok := key.(*mypkg.SuperSecretKey)
// if !ok {
// // You must return jwk.ContinueError here, or otherwise
// // processing will stop with an error
// return nil, fmt.Errorf("invalid key type %T for importer: %w", key, jwk.ContinueError())
// }
//
// return mypkg.SuperSecretJWK{ .... }, nil // You could reuse existing JWK types if you can
// }
//
// ## Registering a KeyExporter
//
// KeyExporters are the opposite of KeyImporters: they convert a JWK to a raw key when `key.Raw(...)` is
// called. If you intend to use `key.Raw(...)` for a JWK created using one of your KeyImporters,
// you will also
//
// KeyExporters are registered by key type. For example, if you want to register a KeyExporter for
// RSA keys, you would do:
//
// jwk.RegisterKeyExporter(jwa.RSA, jwk.KeyExportFunc(exportRSAKey))
//
// For a given JWK, it will be passed a "destination" object to store the exported raw key. For example,
// an RSA-based private JWK can be exported to a `*rsa.PrivateKey` or to a `*any`, but not
// to a `*ecdsa.PrivateKey`:
//
// var dst *rsa.PrivateKey
// key.Raw(&dst) // OK
//
// var dst any
// key.Raw(&dst) // OK
//
// var dst *ecdsa.PrivateKey
// key.Raw(&dst) // Error, if key is an RSA key
//
// You will need to handle this distinction yourself in your KeyImporter. For example, certain
// elliptic curve keys can be expressed in JWK in the same format, minus the "kty". In that case
// you will need to check for the type of the destination object and return an error if it is
// not compatible with your key.
//
// var raw mypkg.PrivateKey // assume a hypothetical private key type using a different curve than standard ones lie P-256
// key, _ := jwk.Import(raw)
// // key could be jwk.ECDSAPrivateKey, with different curve than P-256
//
// var dst *ecdsa.PrivateKey
// key.Raw(&dst) // your KeyImporter will be called with *ecdsa.PrivateKey, which is not compatible with your key
//
// To implement this your code should look like the following:
//
// jwk.RegisterKeyExporter(jwk.EC, jwk.KeyExportFunc(exportMyKey))
//
// func exportMyKey(key jwk.Key, hint any) (any, error) {
// // check if the type of object in hint is compatible with your key
// switch hint.(type) {
// case *mypkg.PrivateKey, *any:
// // OK, we can proceed
// default:
// // Not compatible, return jwk.ContinueError
// return nil, jwk.ContinueError()
// }
//
// // key is a jwk.ECDSAPrivateKey or jwk.ECDSAPublicKey
// switch key := key.(type) {
// case jwk.ECDSAPrivateKey:
// // convert key to mypkg.PrivateKey
// case jwk.ECDSAPublicKey:
// // convert key to mypkg.PublicKey
// default:
// // Not compatible, return jwk.ContinueError
// return nil, jwk.ContinueError()
// }
// return ..., nil
// }
package jwk