forked from MicahParks/keyfunc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jwks.go
131 lines (106 loc) · 3.15 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
120
121
122
123
124
125
126
127
128
129
130
131
package keyfunc
import (
"context"
"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
cancel context.CancelFunc
client *http.Client
ctx context.Context
jwksURL string
mux sync.RWMutex
refreshErrorHandler ErrorHandler
refreshInterval *time.Duration
refreshRateLimit *time.Duration
refreshRequests chan context.CancelFunc
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() {
if j.cancel != nil {
j.cancel()
}
}
// 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 {
// Create a context for refreshing the JWKs.
ctx, cancel := context.WithCancel(j.ctx)
// Refresh the JWKs.
select {
case <-j.ctx.Done():
return
case j.refreshRequests <- cancel:
default:
// If the j.refreshRequests channel is full, return the error early.
return nil, ErrKIDNotFound
}
// Wait for the JWKs refresh to done.
<-ctx.Done()
// 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
}