-
Notifications
You must be signed in to change notification settings - Fork 1
/
jwt.go
424 lines (389 loc) · 11.9 KB
/
jwt.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
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// Copyright 2012 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The jwt package provides support for creating credentials for OAuth2 service
// account requests.
//
// For examples of the package usage please see jwt_test.go.
// Example usage (error handling omitted for brevity):
//
// // Craft the ClaimSet and JWT token.
// t := &jwt.Token{
// Key: pemKeyBytes,
// }
// t.ClaimSet = &jwt.ClaimSet{
// Iss: "XXXXXXXXXXXX@developer.gserviceaccount.com",
// Scope: "https://www.googleapis.com/auth/devstorage.read_only",
// }
//
// // We need to provide a client.
// c := &http.Client{}
//
// // Get the access token.
// o, _ := t.Assert(c)
//
// // Form the request to the service.
// req, _ := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
// req.Header.Set("Authorization", "OAuth "+o.AccessToken)
// req.Header.Set("x-goog-api-version", "2")
// req.Header.Set("x-goog-project-id", "XXXXXXXXXXXX")
//
// // Make the request.
// result, _ := c.Do(req)
//
// For info on OAuth2 service accounts please see the online documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
//
package jwt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"code.google.com/p/goauth2/oauth"
)
// These are the default/standard values for this to work for Google service accounts.
const (
stdAlgorithm = "RS256"
stdType = "JWT"
stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
stdGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
stdAud = "https://accounts.google.com/o/oauth2/token"
)
var (
ErrInvalidKey = errors.New("Invalid Key")
)
// base64Encode returns and Base64url encoded version of the input string with any
// trailing "=" stripped.
func base64Encode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// base64Decode decodes the Base64url encoded string
func base64Decode(s string) ([]byte, error) {
// add back missing padding
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
// The JWT claim set contains information about the JWT including the
// permissions being requested (scopes), the target of the token, the issuer,
// the time the token was issued, and the lifetime of the token.
//
// Aud is usually https://accounts.google.com/o/oauth2/token
type ClaimSet struct {
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
Typ string `json:"typ,omitempty"`
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
PrivateClaims map[string]interface{} `json:"-"`
exp time.Time
iat time.Time
}
// setTimes sets iat and exp to time.Now() and iat.Add(time.Hour) respectively.
//
// Note that these times have nothing to do with the expiration time for the
// access_token returned by the server. These have to do with the lifetime of
// the encoded JWT.
//
// A JWT can be re-used for up to one hour after it was encoded. The access
// token that is granted will also be good for one hour so there is little point
// in trying to use the JWT a second time.
func (c *ClaimSet) setTimes(t time.Time) {
c.iat = t
c.exp = c.iat.Add(time.Hour)
}
var (
jsonStart = []byte{'{'}
jsonEnd = []byte{'}'}
)
// encode returns the Base64url encoded form of the Signature.
func (c *ClaimSet) encode() string {
if c.exp.IsZero() || c.iat.IsZero() {
c.setTimes(time.Now())
}
if c.Aud == "" {
c.Aud = stdAud
}
c.Exp = c.exp.Unix()
c.Iat = c.iat.Unix()
b, err := json.Marshal(c)
if err != nil {
panic(err)
}
if len(c.PrivateClaims) == 0 {
return base64Encode(b)
}
// Marshal private claim set and then append it to b.
prv, err := json.Marshal(c.PrivateClaims)
if err != nil {
panic(fmt.Errorf("Invalid map of private claims %v", c.PrivateClaims))
}
// Concatenate public and private claim JSON objects.
if !bytes.HasSuffix(b, jsonEnd) {
panic(fmt.Errorf("Invalid JSON %s", b))
}
if !bytes.HasPrefix(prv, jsonStart) {
panic(fmt.Errorf("Invalid JSON %s", prv))
}
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
b = append(b, prv[1:]...) // Append private claims.
return base64Encode(b)
}
// Header describes the algorithm and type of token being generated,
// and optionally a KeyID describing additional parameters for the
// signature.
type Header struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
KeyId string `json:"kid,omitempty"`
}
func (h *Header) encode() string {
b, err := json.Marshal(h)
if err != nil {
panic(err)
}
return base64Encode(b)
}
// A JWT is composed of three parts: a header, a claim set, and a signature.
// The well formed and encoded JWT can then be exchanged for an access token.
//
// The Token is not a JWT, but is is encoded to produce a well formed JWT.
//
// When obtaining a key from the Google API console it will be downloaded in a
// PKCS12 encoding. To use this key you will need to convert it to a PEM file.
// This can be achieved with openssl.
//
// $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <key.pem>
//
// The contents of this file can then be used as the Key.
type Token struct {
ClaimSet *ClaimSet // claim set used to construct the JWT
Header *Header // header used to construct the JWT
Key []byte // PEM printable encoding of the private key
pKey *rsa.PrivateKey
header string
claim string
sig string
useExternalSigner bool
signer Signer
}
// NewToken returns a filled in *Token based on the standard header,
// and sets the Iat and Exp times based on when the call to Assert is
// made.
func NewToken(iss, scope string, key []byte) *Token {
c := &ClaimSet{
Iss: iss,
Scope: scope,
Aud: stdAud,
}
h := &Header{
Algorithm: stdAlgorithm,
Type: stdType,
}
t := &Token{
ClaimSet: c,
Header: h,
Key: key,
}
return t
}
// Signer is an interface that given a JWT token, returns the header &
// claim (serialized and urlEncoded to a byte slice), along with the
// signature and an error (if any occured). It could modify any data
// to sign (typically the KeyID).
//
// Example usage where a SHA256 hash of the original url-encoded token
// with an added KeyID and secret data is used as a signature:
//
// var privateData = "secret data added to hash, indexed by KeyID"
//
// type SigningService struct{}
//
// func (ss *SigningService) Sign(in *jwt.Token) (newTokenData, sig []byte, err error) {
// in.Header.KeyID = "signing service"
// newTokenData = in.EncodeWithoutSignature()
// dataToSign := fmt.Sprintf("%s.%s", newTokenData, privateData)
// h := sha256.New()
// _, err := h.Write([]byte(dataToSign))
// sig = h.Sum(nil)
// return
// }
type Signer interface {
Sign(in *Token) (tokenData, signature []byte, err error)
}
// NewSignerToken returns a *Token, using an external signer function
func NewSignerToken(iss, scope string, signer Signer) *Token {
t := NewToken(iss, scope, nil)
t.useExternalSigner = true
t.signer = signer
return t
}
// Expired returns a boolean value letting us know if the token has expired.
func (t *Token) Expired() bool {
return t.ClaimSet.exp.Before(time.Now())
}
// Encode constructs and signs a Token returning a JWT ready to use for
// requesting an access token.
func (t *Token) encode() (string, error) {
var tok string
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
err := t.sign()
if err != nil {
return tok, err
}
tok = fmt.Sprintf("%s.%s.%s", t.header, t.claim, t.sig)
return tok, nil
}
// EncodeWithoutSignature returns the url-encoded value of the Token
// before signing has occured (typically for use by external signers).
func (t *Token) EncodeWithoutSignature() string {
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
return fmt.Sprintf("%s.%s", t.header, t.claim)
}
// sign computes the signature for a Token. The details for this can be found
// in the OAuth2 Service Account documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
func (t *Token) sign() error {
if t.useExternalSigner {
fulldata, sig, err := t.signer.Sign(t)
if err != nil {
return err
}
split := strings.Split(string(fulldata), ".")
if len(split) != 2 {
return errors.New("no token returned")
}
t.header = split[0]
t.claim = split[1]
t.sig = base64Encode(sig)
return err
}
ss := fmt.Sprintf("%s.%s", t.header, t.claim)
if t.pKey == nil {
err := t.parsePrivateKey()
if err != nil {
return err
}
}
h := sha256.New()
h.Write([]byte(ss))
b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil))
t.sig = base64Encode(b)
return err
}
// parsePrivateKey converts the Token's Key ([]byte) into a parsed
// rsa.PrivateKey. If the key is not well formed this method will return an
// ErrInvalidKey error.
func (t *Token) parsePrivateKey() error {
block, _ := pem.Decode(t.Key)
if block == nil {
return ErrInvalidKey
}
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
}
var ok bool
t.pKey, ok = parsedKey.(*rsa.PrivateKey)
if !ok {
return ErrInvalidKey
}
return nil
}
// Assert obtains an *oauth.Token from the remote server by encoding and sending
// a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
// refreshed (no refresh_token is returned with the response). Once this token
// expires call this method again to get a fresh one.
func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
var o *oauth.Token
u, v, err := t.buildRequest()
if err != nil {
return o, err
}
resp, err := c.PostForm(u, v)
if err != nil {
return o, err
}
o, err = handleResponse(resp)
return o, err
}
// buildRequest sets up the URL values and the proper URL string for making our
// access_token request.
func (t *Token) buildRequest() (string, url.Values, error) {
v := url.Values{}
j, err := t.encode()
if err != nil {
return t.ClaimSet.Aud, v, err
}
v.Set("grant_type", stdGrantType)
v.Set("assertion", j)
return t.ClaimSet.Aud, v, nil
}
// Used for decoding the response body.
type respBody struct {
IdToken string `json:"id_token"`
Access string `json:"access_token"`
Type string `json:"token_type"`
ExpiresIn time.Duration `json:"expires_in"`
}
// handleResponse returns a filled in *oauth.Token given the *http.Response from
// a *http.Request created by buildRequest.
func handleResponse(r *http.Response) (*oauth.Token, error) {
o := &oauth.Token{}
defer r.Body.Close()
if r.StatusCode != 200 {
return o, errors.New("invalid response: " + r.Status)
}
b := &respBody{}
err := json.NewDecoder(r.Body).Decode(b)
if err != nil {
return o, err
}
o.AccessToken = b.Access
if b.IdToken != "" {
// decode returned id token to get expiry
o.AccessToken = b.IdToken
s := strings.Split(b.IdToken, ".")
if len(s) < 2 {
return nil, errors.New("invalid token received")
}
d, err := base64Decode(s[1])
if err != nil {
return o, err
}
c := &ClaimSet{}
err = json.NewDecoder(bytes.NewBuffer(d)).Decode(c)
if err != nil {
return o, err
}
o.Expiry = time.Unix(c.Exp, 0)
return o, nil
}
o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
return o, nil
}