Files
Курнат Андрей 2315f25754 Initial QSfera import
2026-06-07 10:20:04 +03:00

245 lines
7.7 KiB
Go

package jwk
import (
"fmt"
"reflect"
"sync"
"github.com/lestrrat-go/blackmagic"
"github.com/lestrrat-go/jwx/v3/internal/json"
"github.com/lestrrat-go/jwx/v3/jwa"
)
// KeyParser represents a type that can parse a JSON representation of a JWK into
// a jwk.Key.
// See KeyConvertor for a type that can convert a raw key into a jwk.Key
type KeyParser interface {
// ParseKey parses a JSON payload to a `jwk.Key` object. The first
// argument is an object that contains some hints as to what kind of
// key the JSON payload contains.
//
// If your KeyParser decides that the payload is not something
// you can parse, and you would like to continue parsing with
// the remaining KeyParser instances that are registered,
// return a `jwk.ContinueParseError`. Any other errors will immediately
// halt the parsing process.
//
// When unmarshaling JSON, use the unmarshaler object supplied as
// the second argument. This will ensure that the JSON is unmarshaled
// in a way that is compatible with the rest of the library.
ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error)
}
// KeyParseFunc is a type of KeyParser that is based on a function/closure
type KeyParseFunc func(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error)
func (f KeyParseFunc) ParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, payload []byte) (Key, error) {
return f(probe, unmarshaler, payload)
}
// protects keyParsers
var muKeyParser sync.RWMutex
// list of parsers
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.
func RegisterKeyParser(kp KeyParser) {
muKeyParser.Lock()
defer muKeyParser.Unlock()
keyParsers = append(keyParsers, kp)
}
func defaultParseKey(probe *KeyProbe, unmarshaler KeyUnmarshaler, data []byte) (Key, error) {
var key Key
var kty string
var d json.RawMessage
if err := probe.Get("Kty", &kty); err != nil {
return nil, fmt.Errorf(`jwk.Parse: failed to get "kty" hint: %w`, err)
}
// We ignore errors from this field, as it's optional
_ = probe.Get("D", &d)
switch v, _ := jwa.LookupKeyType(kty); v {
case jwa.RSA():
if d != nil {
key = newRSAPrivateKey()
} else {
key = newRSAPublicKey()
}
case jwa.EC():
if d != nil {
key = newECDSAPrivateKey()
} else {
key = newECDSAPublicKey()
}
case jwa.OctetSeq():
key = newSymmetricKey()
case jwa.OKP():
if d != nil {
key = newOKPPrivateKey()
} else {
key = newOKPPublicKey()
}
default:
return nil, fmt.Errorf(`invalid key type from JSON (%s)`, kty)
}
if err := unmarshaler.UnmarshalKey(data, key); err != nil {
return nil, fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err)
}
return key, nil
}
type keyUnmarshaler struct {
localReg *json.Registry
}
func (ku *keyUnmarshaler) UnmarshalKey(data []byte, key any) error {
if ku.localReg != nil {
dcKey, ok := key.(json.DecodeCtxContainer)
if !ok {
return fmt.Errorf(`typed field was requested, but the key (%T) does not support DecodeCtx`, key)
}
dc := json.NewDecodeCtx(ku.localReg)
dcKey.SetDecodeCtx(dc)
defer func() { dcKey.SetDecodeCtx(nil) }()
}
if err := json.Unmarshal(data, key); err != nil {
return fmt.Errorf(`failed to unmarshal JSON into key (%T): %w`, key, err)
}
return nil
}
// keyProber is the object that starts the probing. When Probe() is called,
// it creates (possibly from a cached value) an object that is used to
// hold hint values.
type keyProber struct {
mu sync.RWMutex
pool *sync.Pool
fields map[string]reflect.StructField
typ reflect.Type
}
func (kp *keyProber) AddField(field reflect.StructField) error {
kp.mu.Lock()
defer kp.mu.Unlock()
if _, ok := kp.fields[field.Name]; ok {
return fmt.Errorf(`field name %s is already registered`, field.Name)
}
kp.fields[field.Name] = field
kp.makeStructType()
// Update pool (note: the logic is the same, but we need to recreate it
// so that we don't accidentally use old stored values)
kp.pool = &sync.Pool{
New: kp.makeStruct,
}
return nil
}
func (kp *keyProber) makeStructType() {
// DOES NOT LOCK
fields := make([]reflect.StructField, 0, len(kp.fields))
for _, f := range kp.fields {
fields = append(fields, f)
}
kp.typ = reflect.StructOf(fields)
}
func (kp *keyProber) makeStruct() any {
return reflect.New(kp.typ)
}
func (kp *keyProber) Probe(data []byte) (*KeyProbe, error) {
kp.mu.RLock()
defer kp.mu.RUnlock()
// if the field list unchanged, so is the pool object, so effectively
// we should be using the cached version
v := kp.pool.Get()
if v == nil {
return nil, fmt.Errorf(`probe: failed to get object from pool`)
}
rv, ok := v.(reflect.Value)
if !ok {
return nil, fmt.Errorf(`probe: value returned from pool as of type %T, expected reflect.Value`, v)
}
if err := json.Unmarshal(data, rv.Interface()); err != nil {
return nil, fmt.Errorf(`probe: failed to unmarshal data: %w`, err)
}
return &KeyProbe{data: rv}, nil
}
// KeyProbe is the object that carries the hints when parsing a key.
// The exact list of fields can vary depending on the types of key
// that are registered.
//
// Use `Get()` to access the value of a field.
//
// The underlying data stored in a KeyProbe is recycled each
// time a value is parsed, therefore you are not allowed to hold
// onto this object after ParseKey() is done.
type KeyProbe struct {
data reflect.Value
}
// Get returns the value of the field with the given `name“.
// `dst` must be a pointer to a value that can hold the type of
// the value of the field, which is determined by the
// field type registered through `jwk.RegisterProbeField()`
func (kp *KeyProbe) Get(name string, dst any) error {
f := kp.data.Elem().FieldByName(name)
if !f.IsValid() {
return fmt.Errorf(`field %s not found`, name)
}
if err := blackmagic.AssignIfCompatible(dst, f.Addr().Interface()); err != nil {
return fmt.Errorf(`failed to assign value of field %q to %T: %w`, name, dst, err)
}
return nil
}
// We don't really need the object, we need to know its type
var keyProbe = &keyProber{
fields: make(map[string]reflect.StructField),
}
// RegisterProbeField adds a new field to be probed during the initial
// phase of parsing. This is done by partially parsing the JSON payload,
// and we do this by calling `json.Unmarshal` using a dynamic type that
// can possibly be modified during runtime. This function is used to
// add a new field to this dynamic type.
//
// Note that the `Name` field for the given `reflect.StructField` must start
// with an upper case alphabet, such that it is treated as an exported field.
// So for example, if you want to probe the "my_hint" field, you should specify
// the field name as "MyHint" or similar.
//
// Also the field name must be unique. If you believe that your field name may
// collide with other packages that may want to add their own probes,
// it is the responsibility of the caller
// to ensure that the field name is unique (possibly by prefixing the field
// name with a unique string). It is important to note that the field name
// need not be the same as the JSON field name. For example, your field name
// could be "MyPkg_MyHint", while the actual JSON field name could be "my_hint".
//
// If the field name is not unique, an error is returned.
func RegisterProbeField(p reflect.StructField) error {
// locking is done inside keyProbe
return keyProbe.AddField(p)
}
// KeyUnmarshaler is a thin wrapper around json.Unmarshal. It behaves almost
// exactly like json.Unmarshal, but it allows us to add extra magic that
// is specific to this library before calling the actual json.Unmarshal.
type KeyUnmarshaler interface {
UnmarshalKey(data []byte, key any) error
}