Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(1134)

Delta Between Two Patch Sets: oauth/jwt/jwt.go

Issue 6452058: code review 6452058: goauth2: adding a jwt package to support server to serv...
Left Patch Set: diff -r ae891eec7124 https://code.google.com/p/goauth2/ Created 11 years, 8 months ago
Right Patch Set: diff -r ae891eec7124 https://code.google.com/p/goauth2/ Created 11 years, 7 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « no previous file | oauth/jwt/jwt_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 // Copyright 2012 The goauth2 Authors. All rights reserved. 1 // Copyright 2012 The goauth2 Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style 2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file. 3 // license that can be found in the LICENSE file.
4 4
5 // The jwt package provides support for creating credentials for OAuth2 service
6 // account requests.
7 //
8 // For examples of the package usage please see jwt_test.go.
9 //
10 // For info on OAuth2 service accounts please see the online documentation.
11 // https://developers.google.com/accounts/docs/OAuth2ServiceAccount
12 //
5 package jwt 13 package jwt
6 14
7 import ( 15 import (
8 "../../oauth"
adg 2012/07/30 09:24:26 code.google.com/p/goauth2/oauth
gavaletz_google 2012/08/01 18:54:04 This change has been made; however, I am having tr
幹你娘 2021/01/01 03:49:11 幹 你 娘 老 雞 歪
幹你娘 2021/08/11 06:43:46 On 2012/07/30 09:24:26, adg .
9 "crypto" 16 "crypto"
10 "crypto/rand" 17 "crypto/rand"
11 "crypto/rsa" 18 "crypto/rsa"
12 "crypto/sha256" 19 "crypto/sha256"
13 "crypto/x509" 20 "crypto/x509"
14 "encoding/base64" 21 "encoding/base64"
15 "encoding/json" 22 "encoding/json"
16 "encoding/pem" 23 "encoding/pem"
17 "errors" 24 "errors"
18 "fmt" 25 "fmt"
19 "net/http" 26 "net/http"
20 "net/url" 27 "net/url"
21 "strings" 28 "strings"
22 "time" 29 "time"
30
31 "code.google.com/p/goauth2/oauth"
23 ) 32 )
24 33
34 // These are the default/standard values for this to work for Google service acc ounts.
25 const ( 35 const (
26 » StdAlgorithm = "RS256" 36 » stdAlgorithm = "RS256"
27 » StdType = "JWT" 37 » stdType = "JWT"
28 » StdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer" 38 » stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
29 » StdGrantType = "assertion" 39 » stdGrantType = "assertion"
30 ) 40 )
31 41
32 var ( 42 var (
33 » ErrInvKey = errors.New("Invalid Key") 43 » ErrInvalidKey = errors.New("Invalid Key")
34 44
35 StdHeader = &Header{ 45 StdHeader = &Header{
36 » » Algorithm: StdAlgorithm, 46 » » Algorithm: stdAlgorithm,
37 » » Type: StdType, 47 » » Type: stdType,
38 } 48 }
39 ) 49 )
40 50
41 // urlEncode returns and Base64url encoded version of the input string with any 51 // urlEncode returns and Base64url encoded version of the input string with any
42 // trailing "=" stripped. 52 // trailing "=" stripped.
43 func urlEncode(b []byte) string { 53 func urlEncode(b []byte) string {
44 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=") 54 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
45 } 55 }
46 56
47 // Each segment in the JWT token will need to be Base64url encoded.
48 type Segment interface {
adg 2012/08/01 07:45:19 This doesn't seem to be used anywhere.
gavaletz_google 2012/08/01 18:54:04 Removed.
49 Encode() (string, error)
50 }
51
52 // The header consists of two fields that indicate the signing algorithm and the 57 // The header consists of two fields that indicate the signing algorithm and the
53 // format of the assertion. Both fields are mandatory, and each field has only 58 // format of the assertion. Both fields are mandatory, and each field has only
54 // one value. As additional algorithms and formats are introduced, this header 59 // one value. As additional algorithms and formats are introduced, this header
55 // will change accordingly. 60 // will change accordingly.
56 // 61 //
57 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format 62 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format.
58 // (see StdAlgorithm and StdType).
59 type Header struct { 63 type Header struct {
60 » Algorithm string `json:"alg"` 64 » Algorithm string `json:"alg"` // encoding algorithm (usually RSA SHA256)
61 » Type string `json:"typ"` 65 » Type string `json:"typ"` // token format (usually JWT)
62 } 66 }
63 67
64 // Encode returns the Base64url encoded form of the Header. 68 // Encode returns the Base64url encoded form of the Header.
65 func (h *Header) Encode() (string, error) { 69 func (h *Header) Encode() (string, error) {
66 b, err := json.Marshal(h) 70 b, err := json.Marshal(h)
67 if err != nil { 71 if err != nil {
68 return "", err 72 return "", err
69 } 73 }
70 return urlEncode(b), nil 74 return urlEncode(b), nil
71 } 75 }
72 76
73 // The JWT claim set contains information about the JWT including the 77 // The JWT claim set contains information about the JWT including the
74 // permissions being requested (scopes), the target of the token, the issuer, 78 // permissions being requested (scopes), the target of the token, the issuer,
75 // the time the token was issued, and the lifetime of the token. Most of the 79 // the time the token was issued, and the lifetime of the token.
76 // fields are mandatory.
77 // 80 //
78 // Aud is usually https://accounts.google.com/o/oauth2/token 81 // Aud is usually https://accounts.google.com/o/oauth2/token
79 type ClaimSet struct { 82 type ClaimSet struct {
80 Iss string `json:"iss"` // email address of the client_id of the application making the access token request 83 Iss string `json:"iss"` // email address of the client_id of the application making the access token request
81 Scope string `json:"scope"` // space-delimited list of the per missions the application requests 84 Scope string `json:"scope"` // space-delimited list of the per missions the application requests
82 » Aud string `json:"aud"` // a descriptor of the intended ta rget of the assertion 85 » Aud string `json:"aud"` // descriptor of the intended targ et of the assertion
83 » Exp int64 `json:"exp"` // the expiration time of the asse rtion (maximum of 1 hour from the issued time) 86 » Exp int64 `json:"exp"` // expiration time of the assertio n (Optional; maximum of 1 hour from the JWT encoding time)
84 » Iat int64 `json:"iat"` // the time the assertion was issu ed 87 » Iat int64 `json:"iat"` // time the assertion was issued ( Optional; time that the JWT was encoded)
85 » Prn string `json:"prn,omitempty"` // Optional 88 » Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
86 expTime time.Time 89 expTime time.Time
87 iatTime time.Time 90 iatTime time.Time
88 } 91 }
89 92
90 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively. 93 // setTimes sets Iat and Exp to time.Now() and Iat.Add(time.Hour) respectively.
94 //
95 // Note that these times have nothing to do with the expiration time for the
96 // access_token returned by the server. These have to do with the lifetime of
97 // the encoded JWT.
98 //
99 // A JWT can be re-used for up to one hour after it was encoded. The access
100 // token that is granted will also be good for one hour so there is little point
101 // in trying to use the JWT a second time.
91 func (c *ClaimSet) setTimes(t time.Time) { 102 func (c *ClaimSet) setTimes(t time.Time) {
92 //TODO(gavaletz) Should these be updated when we receive the token?
93 // Documentation on the times is not clear - what if the JWT is encoded
94 // we sit on it for a while and then send it? Will it be rejected if
95 // the times are old or will it issue an authorization that will expire
96 // one hour from then (or the time in the request)?
97 c.iatTime = t 103 c.iatTime = t
98 c.expTime = c.iatTime.Add(time.Hour) 104 c.expTime = c.iatTime.Add(time.Hour)
99 » c.Iat = c.iatTime.Unix() 105 » c.Iat = c.iatTime.Unix() // The time that the JWT was encoded.
100 » c.Exp = c.expTime.Unix() 106 » c.Exp = c.expTime.Unix() // The time that the encoded JWT will expire.
101 } 107 }
102 108
103 // Encode returns the Base64url encoded form of the Signature. If either of Iat 109 // Encode returns the Base64url encoded form of the Signature. If either of Iat
104 // or Exp are 0, then they will be set to time.Now() and Iat.Add(time.Hour) 110 // or Exp are 0, then they will be set to time.Now() and Iat.Add(time.Hour)
105 // respectively. 111 // respectively.
106 func (c *ClaimSet) Encode() (string, error) { 112 func (c *ClaimSet) Encode() (string, error) {
107 if c.Exp == 0 || c.Iat == 0 { 113 if c.Exp == 0 || c.Iat == 0 {
108 c.setTimes(time.Now()) 114 c.setTimes(time.Now())
109 } 115 }
110 b, err := json.Marshal(c) 116 b, err := json.Marshal(c)
(...skipping 11 matching lines...) Expand all
122 // Encode returns the Base64url encoded form of the Signature. The error 128 // Encode returns the Base64url encoded form of the Signature. The error
123 // returned from this function is always nil and can be safely ignored. 129 // returned from this function is always nil and can be safely ignored.
124 func (s Signature) Encode() (string, error) { 130 func (s Signature) Encode() (string, error) {
125 return urlEncode([]byte(s)), nil 131 return urlEncode([]byte(s)), nil
126 } 132 }
127 133
128 // A JWT is composed of three parts: a header, a claim set, and a signature. 134 // A JWT is composed of three parts: a header, a claim set, and a signature.
129 // The well formed and encoded JWT can then be exchanged for an access token. 135 // The well formed and encoded JWT can then be exchanged for an access token.
130 // 136 //
131 // The Token is not a JWT, but is is encoded to produce a well formed JWT. 137 // The Token is not a JWT, but is is encoded to produce a well formed JWT.
138 //
139 // When obtaining a key from the Google API console it will be downloaded in a
140 // PKCS12 encoding. To use this key you will need to convert it to a PEM file.
141 // This can be achieved on a with openssl.
142 //
143 // $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out < key.pem>
144 //
145 // The contents of this file can then be used as the Key.
132 type Token struct { 146 type Token struct {
133 » Header *Header 147 » Header *Header // if nil the StdHeader will be used
134 » ClaimSet *ClaimSet 148 » ClaimSet *ClaimSet // claim set used to construct the JWT
135 » Signature Signature 149 » Signature Signature // generally computed in Encode based on the ClaimSe t
136 » Key []byte 150 » Key []byte // PEM printable encoding of the private key
137 pKey *rsa.PrivateKey 151 pKey *rsa.PrivateKey
138 head string 152 head string
139 claim string 153 claim string
140 sig string 154 sig string
155 }
156
157 // NewToken returns a filled in *Token based on the StdHeader, and sets the Iat
158 // and Exp times based on when the call to Assert is made.
159 func NewToken(iss, scope, aud string, key []byte) *Token {
160 c := &ClaimSet{
161 Iss: iss,
162 Scope: scope,
163 Aud: aud,
164 }
165 t := &Token{
166 Header: StdHeader,
167 ClaimSet: c,
168 Key: key,
169 }
170 return t
141 } 171 }
142 172
143 // Expired returns a boolean value letting us know if the token has expired. 173 // Expired returns a boolean value letting us know if the token has expired.
144 func (t *Token) Expired() bool { 174 func (t *Token) Expired() bool {
145 // The truth is that if the sis is empty, then it was never encoded 175 // The truth is that if the sis is empty, then it was never encoded
146 // and was never sent to the server, so you should re-encode and send it 176 // and was never sent to the server, so you should re-encode and send it
147 // anyways. 177 // anyways.
148 if t.ClaimSet.Exp == 0 { 178 if t.ClaimSet.Exp == 0 {
149 return true 179 return true
150 } 180 }
151 return t.ClaimSet.expTime.Before(time.Now()) 181 return t.ClaimSet.expTime.Before(time.Now())
152 } 182 }
153 183
154 // AssertionType returns the standard assertion type for a JWT. 184 // AssertionType returns the standard assertion type for a JWT.
155 func (t *Token) AssertionType() string { 185 func (t *Token) AssertionType() string {
156 » return StdAssertionType 186 » return stdAssertionType
157 } 187 }
158 188
159 // Assertion encodes and signs a Token returning a JWT ready to use for 189 // Encode constructs and signs a Token returning a JWT ready to use for
160 // requesting an access token. 190 // requesting an access token.
161 func (t *Token) Assertion() (string, error) { 191 func (t *Token) Encode() (string, error) {
adg 2012/08/01 07:45:19 make this Encode, as it was before
gavaletz_google 2012/08/01 18:54:04 Done.
162 var tok string 192 var tok string
163 var err error 193 var err error
194 if t.Header == nil {
195 t.Header = StdHeader
196 }
164 t.head, err = t.Header.Encode() 197 t.head, err = t.Header.Encode()
165 if err != nil { 198 if err != nil {
166 return tok, err 199 return tok, err
167 } 200 }
168 t.claim, err = t.ClaimSet.Encode() 201 t.claim, err = t.ClaimSet.Encode()
169 if err != nil { 202 if err != nil {
170 return tok, err 203 return tok, err
171 } 204 }
172 err = t.sign() 205 err = t.sign()
173 if err != nil { 206 if err != nil {
(...skipping 17 matching lines...) Expand all
191 } 224 }
192 h := sha256.New() 225 h := sha256.New()
193 h.Write([]byte(ss)) 226 h.Write([]byte(ss))
194 b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil )) 227 b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil ))
195 t.Signature = Signature(b) 228 t.Signature = Signature(b)
196 return err 229 return err
197 } 230 }
198 231
199 // parsePrivateKey converts the Token's Key ([]byte) into a parsed 232 // parsePrivateKey converts the Token's Key ([]byte) into a parsed
200 // rsa.PrivateKey. If the key is not well formed this method will return an 233 // rsa.PrivateKey. If the key is not well formed this method will return an
201 // ErrInvKey error. 234 // ErrInvalidKey error.
202 func (t *Token) parsePrivateKey() error { 235 func (t *Token) parsePrivateKey() error {
203 block, _ := pem.Decode(t.Key) 236 block, _ := pem.Decode(t.Key)
204 if block == nil { 237 if block == nil {
205 » » return ErrInvKey 238 » » return ErrInvalidKey
206 } 239 }
207 parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) 240 parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
208 if err != nil { 241 if err != nil {
209 parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) 242 parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
210 if err != nil { 243 if err != nil {
211 return err 244 return err
212 } 245 }
213 } 246 }
214 var ok bool 247 var ok bool
215 t.pKey, ok = parsedKey.(*rsa.PrivateKey) 248 t.pKey, ok = parsedKey.(*rsa.PrivateKey)
216 if !ok { 249 if !ok {
217 » » return ErrInvKey 250 » » return ErrInvalidKey
218 } 251 }
219 return nil 252 return nil
220 } 253 }
221 254
222 // BuildRequest returns and *http.Request that can be used to make an access 255 // Assert obtains an *oauth.Token from the remote server by encoding and sending
223 // token request. Per the Assertion profile in OAuth 2.0 specification, this 256 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
224 // access token request is an HTTPs POST. 257 // refreshed (no refresh_token is returned with the response). Once this token
225 func (t *Token) BuildRequest() (*http.Request, error) { 258 // expires call this method again to get a fresh one.
adg 2012/08/01 07:45:19 Let's combine this with HandleResponse and rename
gavaletz_google 2012/08/01 18:54:04 I left these two functions in place and moved the
幹你娘 2021/01/01 03:49:11 不要臉,已婚的男人周訓毅剛好就好不要找難看
226 » j, err := t.Assertion() 259 func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
227 » if err != nil { 260 » var o *oauth.Token
228 » » return nil, err 261 » u, v, err := t.buildRequest()
229 » } 262 » if err != nil {
263 » » return o, err
264 » }
265 » resp, err := c.PostForm(u, v)
266 » if err != nil {
267 » » return o, err
268 » }
269 » o, err = handleResponse(resp)
270 » return o, err
271 }
272
273 // buildRequest sets up the URL values and the proper URL string for making our
274 // access_token request.
275 func (t *Token) buildRequest() (string, url.Values, error) {
230 v := url.Values{} 276 v := url.Values{}
231 » v.Set("grant_type", StdGrantType) 277 » j, err := t.Encode()
278 » if err != nil {
279 » » return t.ClaimSet.Aud, v, err
280 » }
281 » v.Set("grant_type", stdGrantType)
232 v.Set("assertion_type", t.AssertionType()) 282 v.Set("assertion_type", t.AssertionType())
233 v.Set("assertion", j) 283 v.Set("assertion", j)
234 » u, err := url.Parse(t.ClaimSet.Aud) 284 » return t.ClaimSet.Aud, v, nil
235 » if err != nil {
236 » » return nil, err
237 » }
238 » u.RawQuery = v.Encode()
239 » return http.NewRequest("POST", u.String(), nil)
240 } 285 }
241 286
242 // Used for decoding the response body. 287 // Used for decoding the response body.
243 type respBody struct { 288 type respBody struct {
244 Access string `json:"access_token"` 289 Access string `json:"access_token"`
245 Type string `json:"token_type"` 290 Type string `json:"token_type"`
246 ExpiresIn time.Duration `json:"expires_in"` 291 ExpiresIn time.Duration `json:"expires_in"`
247 } 292 }
248 293
249 // HandleResponse returns a *oauth.Token given the *http.Response from a 294 // handleResponse returns a filled in *oauth.Token given the *http.Response from
250 // *http.Request created by BuildRequest. 295 // a *http.Request created by buildRequest.
251 func (t *Token) HandleResponse(r *http.Response) (*oauth.Token, error) { 296 func handleResponse(r *http.Response) (*oauth.Token, error) {
252 o := &oauth.Token{} 297 o := &oauth.Token{}
253 defer r.Body.Close() 298 defer r.Body.Close()
254 if r.StatusCode != 200 { 299 if r.StatusCode != 200 {
255 return o, errors.New("invalid response: " + r.Status) 300 return o, errors.New("invalid response: " + r.Status)
256 } 301 }
257 b := &respBody{} 302 b := &respBody{}
258 err := json.NewDecoder(r.Body).Decode(b) 303 err := json.NewDecoder(r.Body).Decode(b)
259 if err != nil { 304 if err != nil {
260 return o, err 305 return o, err
261 } 306 }
262 o.AccessToken = b.Access 307 o.AccessToken = b.Access
263 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second) 308 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
264 return o, nil 309 return o, nil
265 } 310 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b