-
Notifications
You must be signed in to change notification settings - Fork 3
/
encoder.go
173 lines (142 loc) · 4.68 KB
/
encoder.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package jwt
import (
"crypto/elliptic"
"sync"
"time"
"github.com/go-errors/errors"
"github.com/golang-jwt/jwt/v5"
"github.com/patrickmn/go-cache"
)
const (
privCacheKey = "encoder_priv_key"
defaultEncoderExpiration = 60 * time.Minute
defaultEncoderCleanupInterval = 1 * time.Minute
invalidKey = iota
rsaKey
ecdsaKey512
ecdsaKey384
ecdsaKey256
)
type (
privateKey interface{} // Only ECDSA (perferred) and RSA public keys allowed
EncoderKeyRetriever func() (string, string)
)
type encoderPrivateKey struct {
privateSigningKey privateKey
keyType int
kid string
}
// JwtEncoder can encode a claim to a jwt token string.
type JwtEncoder struct {
fetchPrivateKey EncoderKeyRetriever // func provided by clients of this library to supply a refreshed private key and kid
mu sync.Mutex // mutex to protect cache.Get/Set race condition
cache *cache.Cache // memory cache holding the encoderPrivateKey struct
defaultExpiration time.Duration // default is 60 minutes
cleanupInterval time.Duration // default is every 1 minute
}
// NewJwtEncoder creates a new JwtEncoder.
func NewJwtEncoder(fetchPrivateKey EncoderKeyRetriever, options ...JwtEncoderOption) (*JwtEncoder, error) {
encoder := &JwtEncoder{
fetchPrivateKey: fetchPrivateKey,
defaultExpiration: defaultEncoderExpiration,
cleanupInterval: defaultEncoderCleanupInterval,
}
// Loop through our Encoder options and apply them
for _, option := range options {
option(encoder)
}
encoder.cache = cache.New(encoder.defaultExpiration, encoder.cleanupInterval)
// call the fetchPrivateKey func to make sure the private key is valid
_, err := encoder.loadPrivateKey()
if err != nil {
return nil, errors.Errorf("failed to load private key: %w", err)
}
return encoder, nil
}
// Encode the Standard Culture Amp Claims in a jwt token string.
func (e *JwtEncoder) Encode(claims *StandardClaims) (string, error) {
var token *jwt.Token
registerClaims := newEncoderClaims(claims)
// Will check cache and re-fetch if expired
encodingKey, err := e.loadPrivateKey()
if err != nil {
return "", errors.Errorf("failed to load private key: %w", err)
}
switch encodingKey.keyType {
case ecdsaKey512:
token = jwt.NewWithClaims(jwt.SigningMethodES512, registerClaims)
case ecdsaKey384:
token = jwt.NewWithClaims(jwt.SigningMethodES384, registerClaims)
case ecdsaKey256:
token = jwt.NewWithClaims(jwt.SigningMethodES256, registerClaims)
case rsaKey:
token = jwt.NewWithClaims(jwt.SigningMethodRS512, registerClaims)
default:
return "", errors.Errorf("Only ECDSA and RSA private keys are supported")
}
if encodingKey.kid != "" {
token.Header[kidHeaderKey] = encodingKey.kid
}
return token.SignedString(encodingKey.privateSigningKey)
}
func (e *JwtEncoder) loadPrivateKey() (*encoderPrivateKey, error) {
// First chech cache, if its there then great, use it!
if key, ok := e.getCachedPrivateKey(); ok {
return key, nil
}
// Only allow one thread to refetch, parse and update the cache
e.mu.Lock()
defer e.mu.Unlock()
// check the cache again in case another go routine just updated it
if key, ok := e.getCachedPrivateKey(); ok {
return key, nil
}
// Call client retriever func
privateKey, kid := e.fetchPrivateKey()
// check its valid by parsing the PEM key
encodingKey, err := e.parsePrivateKey(privateKey, kid)
if err != nil {
return nil, err
}
// Add back into the cache
err = e.cache.Add(privCacheKey, encodingKey, cache.DefaultExpiration)
return encodingKey, err
}
func (e *JwtEncoder) getCachedPrivateKey() (*encoderPrivateKey, bool) {
// First chech cache, if its there then great, use it!
obj, found := e.cache.Get(privCacheKey)
if !found {
return nil, false
}
key, ok := obj.(*encoderPrivateKey)
return key, ok
}
func (e *JwtEncoder) parsePrivateKey(privKey string, kid string) (*encoderPrivateKey, error) {
privatePEMKey := []byte(privKey)
ecdaPrivateKey, err := jwt.ParseECPrivateKeyFromPEM(privatePEMKey)
if err == nil {
edcdaKey := &encoderPrivateKey{
privateSigningKey: ecdaPrivateKey,
kid: kid,
}
switch ecdaPrivateKey.Curve {
case elliptic.P256():
edcdaKey.keyType = ecdsaKey256
case elliptic.P384():
edcdaKey.keyType = ecdsaKey384
default:
edcdaKey.keyType = ecdsaKey512
}
return edcdaKey, nil
}
rsaPrivateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privatePEMKey)
if err == nil {
return &encoderPrivateKey{
privateSigningKey: rsaPrivateKey,
keyType: rsaKey,
kid: kid,
}, nil
}
// add other key types in the future
return nil, errors.Errorf("invalid private key: only ECDSA and RSA private keys are supported")
}