forked from MicahParks/keyfunc
/
jwks.go
119 lines (97 loc) · 2.86 KB
/
jwks.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package keyfunc
import (
"encoding/json"
"errors"
"net/http"
"sync"
"time"
)
var (
// ErrKIDNotFound indicates that the given key ID was not found in the JWKS.
ErrKIDNotFound = errors.New("the given key ID was not found in the JWKS")
// ErrMissingAssets indicates there are required assets missing to create a public key.
ErrMissingAssets = errors.New("required assets are missing to create a public key")
)
// ErrorHandler is a function signature that consumes an error.
type ErrorHandler func(err error)
// JSONKey represents a raw key inside a JWKS.
type JSONKey struct {
Curve string `json:"crv"`
Exponent string `json:"e"`
ID string `json:"kid"`
Modulus string `json:"n"`
X string `json:"x"`
Y string `json:"y"`
precomputed interface{}
}
// JWKS represents a JSON Web Key Set.
type JWKS struct {
Keys map[string]*JSONKey
client *http.Client
endBackground chan struct{}
endOnce sync.Once
jwksURL string
mux sync.RWMutex
refreshErrorHandler ErrorHandler
refreshInterval *time.Duration
refreshTimeout *time.Duration
refreshUnknownKID bool
}
// rawJWKS represents a JWKS in JSON format.
type rawJWKS struct {
Keys []JSONKey `json:"keys"`
}
// New creates a new JWKS from a raw JSON message.
func New(jwksBytes json.RawMessage) (jwks *JWKS, err error) {
// Turn the raw JWKS into the correct Go type.
var rawKS rawJWKS
if err = json.Unmarshal(jwksBytes, &rawKS); err != nil {
return nil, err
}
// Iterate through the keys in the raw JWKS. Add them to the JWKS.
jwks = &JWKS{
Keys: make(map[string]*JSONKey, len(rawKS.Keys)),
}
for _, key := range rawKS.Keys {
key := key
jwks.Keys[key.ID] = &key
}
return jwks, nil
}
// EndBackground ends the background goroutine to update the JWKs. It can only happen once and is only effective if the
// JWKS has a background goroutine refreshing the JWKS keys.
func (j *JWKS) EndBackground() {
j.endOnce.Do(func() {
if j.endBackground != nil {
close(j.endBackground)
}
})
}
// getKey gets the JSONKey from the given KID from the JWKS. It may refresh the JWKS if configured to.
func (j *JWKS) getKey(kid string) (jsonKey *JSONKey, err error) {
// Get the JSONKey from the JWKS.
var ok bool
j.mux.RLock()
jsonKey, ok = j.Keys[kid]
j.mux.RUnlock()
// Check if the key was present.
if !ok {
// Check to see if configured to refresh on unknown kid.
if j.refreshUnknownKID {
// Refresh the JWKS.
if err = j.refresh(); err != nil && j.refreshErrorHandler != nil {
j.refreshErrorHandler(err)
err = nil
}
// Lock the JWKS for async safe use.
j.mux.RLock()
defer j.mux.RUnlock()
// Check if the JWKS refresh contained the requested key.
if jsonKey, ok = j.Keys[kid]; ok {
return jsonKey, nil
}
}
return nil, ErrKIDNotFound
}
return jsonKey, nil
}