-
Notifications
You must be signed in to change notification settings - Fork 20
/
Copy pathjwk.go
334 lines (284 loc) · 7.9 KB
/
jwk.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
package jwt
import (
"crypto/ecdsa"
"crypto/ed25519"
"crypto/elliptic"
"crypto/rsa"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
)
// FetchPublicKeys fetches the JSON Web Key Set (JWKS) from the given URL
// and returns the public keys as Keys map.
// It returns an error if the request fails or the JWKS is invalid.
//
// The url is the URL of the JWKS endpoint,
// usually ends with: /.well-known/jwks.json.
//
// It supports all RS256, RS384, RS512, ES256, ES384, ES512 and Ed25519 algorithms.
func FetchPublicKeys(url string) (Keys, error) {
set, err := FetchJWKS(http.DefaultClient, url)
if err != nil {
return nil, err
}
return set.PublicKeys(), nil
}
// JWKS represents a JSON Web Key Set.
type JWKS struct {
Keys []*JWK `json:"keys"`
}
// PublicKeys parse and returns the public keys as Keys map from the JSON Web Key Set (JWKS).
// It supports all RS256, RS384, RS512, ES256, ES384, ES512 and Ed25519 algorithms.
func (set *JWKS) PublicKeys() Keys {
keys := make(Keys, len(set.Keys))
for _, key := range set.Keys {
alg := parseAlg(key.Alg)
if alg == nil {
continue
}
publicKey, err := convertJWKToPublicKey(key)
if err != nil {
continue
}
keys[key.Kid] = &Key{
ID: key.Kid,
Alg: alg,
Public: publicKey,
}
}
return keys
}
type httpError struct {
StatusCode int
Body []byte
}
func (err httpError) Error() string {
return fmt.Sprintf("status code: %d: body: %s",
err.StatusCode, string(err.Body))
}
// FetchJWKS fetches the JSON Web Key Set (JWKS) from the given URL.
// It returns the JWKS object or an error if the request fails.
// If the HTTP client is not set, the default http.Client is used.
//
// The url is the URL of the JWKS endpoint,
// usually ends with: /.well-known/jwks.json.
func FetchJWKS(client HTTPClient, url string) (*JWKS, error) {
if client == nil {
client = http.DefaultClient
}
resp, err := client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
b, _ := io.ReadAll(resp.Body) // ignore error.
return nil, httpError{StatusCode: resp.StatusCode, Body: b}
}
var jwkSet JWKS
err = json.NewDecoder(resp.Body).Decode(&jwkSet)
if err != nil {
return nil, err
}
return &jwkSet, nil
}
//
// convert jwk to public key.
//
type (
// HTTPClient is an interface that can be used to mock the http.Client.
// It is used to fetch the JSON Web Key Set (JWKS) from AWS Cognito.
HTTPClient interface {
Get(string) (*http.Response, error)
}
// JWK represents a JSON Web Key.
JWK struct {
Kty string `json:"kty"` // Key type (e.g., "RSA", "OKP" Octet Key Pair)
Kid string `json:"kid"` // Key ID
Use string `json:"use"` // Key use (e.g., "sig")
Alg string `json:"alg"` // Algorithm (e.g., "RS256", "EdDSA")
Crv string `json:"crv"` // Curve name (e.g., "Ed25519")
N string `json:"n"` // RSA modulus (Base64 URL-encoded)
E string `json:"e"` // RSA exponent (Base64 URL-encoded)
Y string `json:"y"` // Elliptic y-coordinate (Base64 URL-encoded)
X string `json:"x"` // EdDSA public key (Base64 URL-encoded)
}
)
func convertJWKToPublicKey(jwk *JWK) (PublicKey, error) {
// Parse the key based on its type
switch jwk.Kty {
case "RSA":
publicKey, err := convertJWKToPublicKeyRSA(jwk)
if err != nil {
return nil, fmt.Errorf("parse RSA key: %w", err)
}
return publicKey, nil
case "EC":
publicKey, err := convertJWKToPublicKeyEC(jwk)
if err != nil {
return nil, fmt.Errorf("parse EC key: %w", err)
}
return publicKey, nil
case "OKP":
publicKey, err := convertJWKToPublicKeyEdDSA(jwk)
if err != nil {
return nil, fmt.Errorf("parse EdDSA key: %w", err)
}
return publicKey, nil
default:
return nil, fmt.Errorf("unsupported key type: %s", jwk.Kty)
}
}
// convertJWKToPublicKey converts a JWK object to a *rsa.PublicKey object.
func convertJWKToPublicKeyRSA(jwk *JWK) (*rsa.PublicKey, error) {
// decode the n and e values from base64.
nBytes, err := base64.RawURLEncoding.DecodeString(jwk.N)
if err != nil {
return nil, err
}
eBytes, err := base64.RawURLEncoding.DecodeString(jwk.E)
if err != nil {
return nil, err
}
// construct a big.Int from the n bytes.
n := new(big.Int).SetBytes(nBytes)
// construct an int from the e bytes.
var e int
for _, b := range eBytes {
e = e<<8 + int(b)
}
// or: e := int(new(big.Int).SetBytes(eBytes).Int64())
// construct a *rsa.PublicKey from the n and e values.
pubKey := &rsa.PublicKey{
N: n,
E: e,
}
return pubKey, nil
}
func convertJWKToPublicKeyEC(jwk *JWK) (*ecdsa.PublicKey, error) {
// Check key type
if jwk.Kty != "EC" {
return nil, fmt.Errorf("invalid key type: expected EC")
}
// Decode x and y coordinates
xBytes, err := base64.RawURLEncoding.DecodeString(jwk.X)
if err != nil {
return nil, fmt.Errorf("failed to decode x-coordinate")
}
yBytes, err := base64.RawURLEncoding.DecodeString(jwk.Y)
if err != nil {
return nil, fmt.Errorf("failed to decode y-coordinate")
}
// Convert x and y to big.Int
x := new(big.Int).SetBytes(xBytes)
y := new(big.Int).SetBytes(yBytes)
// Determine the elliptic curve
var curve elliptic.Curve
switch jwk.Crv {
case "P-256":
curve = elliptic.P256()
case "P-384":
curve = elliptic.P384()
case "P-521":
curve = elliptic.P521()
default:
return nil, fmt.Errorf("unsupported elliptic curve")
}
// Reconstruct the public key
publicKey := &ecdsa.PublicKey{
Curve: curve,
X: x,
Y: y,
}
return publicKey, nil
}
func convertJWKToPublicKeyEdDSA(jwk *JWK) (ed25519.PublicKey, error) {
xBytes, err := base64.RawURLEncoding.DecodeString(jwk.X)
if err != nil {
return nil, err
}
publicKey := ed25519.PublicKey(xBytes)
return publicKey, nil
}
//
// convert public key to JWK.
//
// GenerateJWK generates a JSON Web Key (JWK) from the given public key.
// Supported public key types:
//
// 1. RSA (RS256, RS384, RS512) as *rsa.PublicKey.
// 2. Elliptic Curve (ES256, ES384, ES512) as ecdsa.PublicKey.
// 3. EdDSA as ed25519.PublicKey.
func GenerateJWK(kid string, alg string, publicKey PublicKey) (*JWK, error) {
switch publicKey := publicKey.(type) {
case *rsa.PublicKey:
return generateJWKFromPublicKeyRSA(kid, alg, publicKey), nil
case ecdsa.PublicKey:
return generateJWKFromPublicKeyEC(kid, alg, publicKey)
case ed25519.PublicKey:
return generateJWKFromPublicKeyEdDSA(kid, publicKey), nil
default:
return nil, fmt.Errorf("unsupported public key type: %T", publicKey)
}
}
func generateJWKFromPublicKeyRSA(kid string, alg string, publicKey *rsa.PublicKey) *JWK {
// Extract modulus (n) and exponent (e).
n := base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes())
e := base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes())
// Create JWK
jwk := JWK{
Kty: "RSA",
Kid: kid,
Use: "sig",
Alg: alg,
N: n,
E: e,
}
return &jwk
}
func generateJWKFromPublicKeyEC(kid string, alg string, publicKey ecdsa.PublicKey) (*JWK, error) {
// Get the curve parameters.
x := base64.RawURLEncoding.EncodeToString(publicKey.X.Bytes())
y := base64.RawURLEncoding.EncodeToString(publicKey.Y.Bytes())
// Determine the curve name.
var crv string
switch publicKey.Curve {
case elliptic.P256():
crv = "P-256"
case elliptic.P384():
crv = "P-384"
case elliptic.P521():
crv = "P-521"
default:
return nil, fmt.Errorf("unsupported elliptic curve: %s", publicKey.Curve.Params().Name)
}
jwk := JWK{
Kty: "EC",
Kid: kid,
Use: "sig",
Alg: alg, // e.g., "ES256", "ES384", "ES512"
Crv: crv,
X: x,
Y: y,
}
return &jwk, nil
}
func generateJWKFromPublicKeyEdDSA(kid string, publicKey ed25519.PublicKey) *JWK {
// Base64 URL-encode the public key.
x := base64.RawURLEncoding.EncodeToString(publicKey)
// Create JWK
jwk := JWK{
Kty: "OKP",
Kid: kid,
Use: "sig",
Alg: "EdDSA",
Crv: "Ed25519",
X: x,
}
return &jwk
}
// HMAC is a symmetric algorithm, so it doesn’t use JWKS (which is for public keys).
// Instead, you share the secret key securely between parties.