320 lines
6.2 KiB
Go
320 lines
6.2 KiB
Go
package jwk
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"maps"
|
|
"reflect"
|
|
"sort"
|
|
|
|
"github.com/lestrrat-go/blackmagic"
|
|
"github.com/lestrrat-go/jwx/v3/internal/json"
|
|
"github.com/lestrrat-go/jwx/v3/internal/pool"
|
|
"github.com/lestrrat-go/jwx/v3/internal/tokens"
|
|
)
|
|
|
|
const keysKey = `keys` // appease linter
|
|
|
|
func newSet() *set {
|
|
return &set{
|
|
privateParams: make(map[string]any),
|
|
}
|
|
}
|
|
|
|
// NewSet creates and empty `jwk.Set` object
|
|
func NewSet() Set {
|
|
return newSet()
|
|
}
|
|
|
|
func (s *set) Set(n string, v any) error {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
if n == keysKey {
|
|
vl, ok := v.([]Key)
|
|
if !ok {
|
|
return fmt.Errorf(`value for field "keys" must be []jwk.Key`)
|
|
}
|
|
s.keys = vl
|
|
return nil
|
|
}
|
|
|
|
s.privateParams[n] = v
|
|
return nil
|
|
}
|
|
|
|
func (s *set) Get(name string, dst any) error {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
v, ok := s.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 to dst: %w`, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *set) Key(idx int) (Key, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
if idx >= 0 && idx < len(s.keys) {
|
|
return s.keys[idx], true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (s *set) Len() int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return len(s.keys)
|
|
}
|
|
|
|
// indexNL is Index(), but without the locking
|
|
func (s *set) indexNL(key Key) int {
|
|
for i, k := range s.keys {
|
|
if k == key {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (s *set) Index(key Key) int {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
return s.indexNL(key)
|
|
}
|
|
|
|
func (s *set) AddKey(key Key) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if reflect.ValueOf(key).IsNil() {
|
|
panic("nil key")
|
|
}
|
|
|
|
if i := s.indexNL(key); i > -1 {
|
|
return fmt.Errorf(`(jwk.Set).AddKey: key already exists`)
|
|
}
|
|
s.keys = append(s.keys, key)
|
|
return nil
|
|
}
|
|
|
|
func (s *set) Remove(name string) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
delete(s.privateParams, name)
|
|
return nil
|
|
}
|
|
|
|
func (s *set) RemoveKey(key Key) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
for i, k := range s.keys {
|
|
if k == key {
|
|
switch i {
|
|
case 0:
|
|
s.keys = s.keys[1:]
|
|
case len(s.keys) - 1:
|
|
s.keys = s.keys[:i]
|
|
default:
|
|
s.keys = append(s.keys[:i], s.keys[i+1:]...)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf(`(jwk.Set).RemoveKey: specified key does not exist in set`)
|
|
}
|
|
|
|
func (s *set) Clear() error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.keys = nil
|
|
s.privateParams = make(map[string]any)
|
|
return nil
|
|
}
|
|
|
|
func (s *set) Keys() []string {
|
|
ret := make([]string, len(s.privateParams))
|
|
var i int
|
|
for k := range s.privateParams {
|
|
ret[i] = k
|
|
i++
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func (s *set) MarshalJSON() ([]byte, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
buf := pool.BytesBuffer().Get()
|
|
defer pool.BytesBuffer().Put(buf)
|
|
enc := json.NewEncoder(buf)
|
|
|
|
fields := []string{keysKey}
|
|
for k := range s.privateParams {
|
|
fields = append(fields, k)
|
|
}
|
|
sort.Strings(fields)
|
|
|
|
buf.WriteByte(tokens.OpenCurlyBracket)
|
|
for i, field := range fields {
|
|
if i > 0 {
|
|
buf.WriteByte(tokens.Comma)
|
|
}
|
|
fmt.Fprintf(buf, `%q:`, field)
|
|
if field != keysKey {
|
|
if err := enc.Encode(s.privateParams[field]); err != nil {
|
|
return nil, fmt.Errorf(`failed to marshal field %q: %w`, field, err)
|
|
}
|
|
} else {
|
|
buf.WriteByte(tokens.OpenSquareBracket)
|
|
for j, k := range s.keys {
|
|
if j > 0 {
|
|
buf.WriteByte(tokens.Comma)
|
|
}
|
|
if err := enc.Encode(k); err != nil {
|
|
return nil, fmt.Errorf(`failed to marshal key #%d: %w`, i, err)
|
|
}
|
|
}
|
|
buf.WriteByte(tokens.CloseSquareBracket)
|
|
}
|
|
}
|
|
buf.WriteByte(tokens.CloseCurlyBracket)
|
|
|
|
ret := make([]byte, buf.Len())
|
|
copy(ret, buf.Bytes())
|
|
return ret, nil
|
|
}
|
|
|
|
func (s *set) UnmarshalJSON(data []byte) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.privateParams = make(map[string]any)
|
|
s.keys = nil
|
|
|
|
var options []ParseOption
|
|
var ignoreParseError bool
|
|
if dc := s.dc; dc != nil {
|
|
if localReg := dc.Registry(); localReg != nil {
|
|
options = append(options, withLocalRegistry(localReg))
|
|
}
|
|
ignoreParseError = dc.IgnoreParseError()
|
|
}
|
|
|
|
var sawKeysField bool
|
|
dec := json.NewDecoder(bytes.NewReader(data))
|
|
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:
|
|
switch tok {
|
|
case "keys":
|
|
sawKeysField = true
|
|
var list []json.RawMessage
|
|
if err := dec.Decode(&list); err != nil {
|
|
return fmt.Errorf(`failed to decode "keys": %w`, err)
|
|
}
|
|
|
|
for i, keysrc := range list {
|
|
key, err := ParseKey(keysrc, options...)
|
|
if err != nil {
|
|
if !ignoreParseError {
|
|
return fmt.Errorf(`failed to decode key #%d in "keys": %w`, i, err)
|
|
}
|
|
continue
|
|
}
|
|
s.keys = append(s.keys, key)
|
|
}
|
|
default:
|
|
var v any
|
|
if err := dec.Decode(&v); err != nil {
|
|
return fmt.Errorf(`failed to decode value for key %q: %w`, tok, err)
|
|
}
|
|
s.privateParams[tok] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is really silly, but we can only detect the
|
|
// lack of the "keys" field after going through the
|
|
// entire object once
|
|
// Not checking for len(s.keys) == 0, because it could be
|
|
// an empty key set
|
|
if !sawKeysField {
|
|
key, err := ParseKey(data, options...)
|
|
if err != nil {
|
|
return fmt.Errorf(`failed to parse sole key in key set`)
|
|
}
|
|
s.keys = append(s.keys, key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *set) LookupKeyID(kid string) (Key, bool) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
for i := range s.Len() {
|
|
key, ok := s.Key(i)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
gotkid, ok := key.KeyID()
|
|
if ok && gotkid == kid {
|
|
return key, true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
func (s *set) DecodeCtx() DecodeCtx {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.dc
|
|
}
|
|
|
|
func (s *set) SetDecodeCtx(dc DecodeCtx) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.dc = dc
|
|
}
|
|
|
|
func (s *set) Clone() (Set, error) {
|
|
s2 := newSet()
|
|
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
s2.keys = make([]Key, len(s.keys))
|
|
copy(s2.keys, s.keys)
|
|
|
|
maps.Copy(s2.privateParams, s.privateParams)
|
|
|
|
return s2, nil
|
|
}
|