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

Side by Side Diff: oauth/jwt/jwt.go

Issue 6452058: code review 6452058: goauth2: adding a jwt package to support server to serv...
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:
View unified diff | Download patch
« no previous file with comments | « no previous file | oauth/jwt/jwt_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2012 The goauth2 Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
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 //
13 package jwt
14
15 import (
16 "crypto"
17 "crypto/rand"
18 "crypto/rsa"
19 "crypto/sha256"
20 "crypto/x509"
21 "encoding/base64"
22 "encoding/json"
23 "encoding/pem"
24 "errors"
25 "fmt"
26 "net/http"
27 "net/url"
28 "strings"
29 "time"
30
31 "code.google.com/p/goauth2/oauth"
32 )
33
34 // These are the default/standard values for this to work for Google service acc ounts.
35 const (
36 stdAlgorithm = "RS256"
37 stdType = "JWT"
38 stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
39 stdGrantType = "assertion"
40 )
41
42 var (
43 ErrInvalidKey = errors.New("Invalid Key")
44
45 StdHeader = &Header{
46 Algorithm: stdAlgorithm,
47 Type: stdType,
48 }
49 )
50
51 // urlEncode returns and Base64url encoded version of the input string with any
52 // trailing "=" stripped.
53 func urlEncode(b []byte) string {
54 return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
55 }
56
57 // The header consists of two fields that indicate the signing algorithm and the
58 // format of the assertion. Both fields are mandatory, and each field has only
59 // one value. As additional algorithms and formats are introduced, this header
60 // will change accordingly.
61 //
62 // Service Accounts rely on the RSA SHA256 algorithm and the JWT token format.
63 type Header struct {
64 Algorithm string `json:"alg"` // encoding algorithm (usually RSA SHA256)
65 Type string `json:"typ"` // token format (usually JWT)
66 }
67
68 // Encode returns the Base64url encoded form of the Header.
69 func (h *Header) Encode() (string, error) {
70 b, err := json.Marshal(h)
71 if err != nil {
72 return "", err
73 }
74 return urlEncode(b), nil
75 }
76
77 // The JWT claim set contains information about the JWT including the
78 // permissions being requested (scopes), the target of the token, the issuer,
79 // the time the token was issued, and the lifetime of the token.
80 //
81 // Aud is usually https://accounts.google.com/o/oauth2/token
82 type ClaimSet struct {
83 Iss string `json:"iss"` // email address of the client_id of the application making the access token request
84 Scope string `json:"scope"` // space-delimited list of the per missions the application requests
85 Aud string `json:"aud"` // descriptor of the intended targ et of the assertion
86 Exp int64 `json:"exp"` // expiration time of the assertio n (Optional; maximum of 1 hour from the JWT encoding time)
87 Iat int64 `json:"iat"` // time the assertion was issued ( Optional; time that the JWT was encoded)
88 Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
89 expTime time.Time
90 iatTime time.Time
91 }
92
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.
102 func (c *ClaimSet) setTimes(t time.Time) {
103 c.iatTime = t
104 c.expTime = c.iatTime.Add(time.Hour)
105 c.Iat = c.iatTime.Unix() // The time that the JWT was encoded.
106 c.Exp = c.expTime.Unix() // The time that the encoded JWT will expire.
107 }
108
109 // Encode returns the Base64url encoded form of the Signature. If either of Iat
110 // or Exp are 0, then they will be set to time.Now() and Iat.Add(time.Hour)
111 // respectively.
112 func (c *ClaimSet) Encode() (string, error) {
113 if c.Exp == 0 || c.Iat == 0 {
114 c.setTimes(time.Now())
115 }
116 b, err := json.Marshal(c)
117 if err != nil {
118 return "", err
119 }
120 return urlEncode(b), nil
121 }
122
123 // JSON Web Signature (JWS) is the specification that guides the mechanics of
124 // generating the signature for the JWT. The contents of a Signature will be
125 // generated based on a well formed Header and ClaimSet.
126 type Signature []byte
127
128 // Encode returns the Base64url encoded form of the Signature. The error
129 // returned from this function is always nil and can be safely ignored.
130 func (s Signature) Encode() (string, error) {
131 return urlEncode([]byte(s)), nil
132 }
133
134 // A JWT is composed of three parts: a header, a claim set, and a signature.
135 // The well formed and encoded JWT can then be exchanged for an access token.
136 //
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.
146 type Token struct {
147 Header *Header // if nil the StdHeader will be used
148 ClaimSet *ClaimSet // claim set used to construct the JWT
149 Signature Signature // generally computed in Encode based on the ClaimSe t
150 Key []byte // PEM printable encoding of the private key
151 pKey *rsa.PrivateKey
152 head string
153 claim 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
171 }
172
173 // Expired returns a boolean value letting us know if the token has expired.
174 func (t *Token) Expired() bool {
175 // The truth is that if the sis is empty, then it was never encoded
176 // and was never sent to the server, so you should re-encode and send it
177 // anyways.
178 if t.ClaimSet.Exp == 0 {
179 return true
180 }
181 return t.ClaimSet.expTime.Before(time.Now())
182 }
183
184 // AssertionType returns the standard assertion type for a JWT.
185 func (t *Token) AssertionType() string {
186 return stdAssertionType
187 }
188
189 // Encode constructs and signs a Token returning a JWT ready to use for
190 // requesting an access token.
191 func (t *Token) Encode() (string, error) {
192 var tok string
193 var err error
194 if t.Header == nil {
195 t.Header = StdHeader
196 }
197 t.head, err = t.Header.Encode()
198 if err != nil {
199 return tok, err
200 }
201 t.claim, err = t.ClaimSet.Encode()
202 if err != nil {
203 return tok, err
204 }
205 err = t.sign()
206 if err != nil {
207 return tok, err
208 }
209 t.sig, _ = t.Signature.Encode()
210 tok = fmt.Sprintf("%s.%s.%s", t.head, t.claim, t.sig)
211 return tok, nil
212 }
213
214 // sign computes the signature for a Token. The details for this can be found
215 // in the OAuth2 Service Account documentation.
216 // https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsig nature
217 func (t *Token) sign() error {
218 ss := fmt.Sprintf("%s.%s", t.head, t.claim)
219 if t.pKey == nil {
220 err := t.parsePrivateKey()
221 if err != nil {
222 return err
223 }
224 }
225 h := sha256.New()
226 h.Write([]byte(ss))
227 b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil ))
228 t.Signature = Signature(b)
229 return err
230 }
231
232 // parsePrivateKey converts the Token's Key ([]byte) into a parsed
233 // rsa.PrivateKey. If the key is not well formed this method will return an
234 // ErrInvalidKey error.
235 func (t *Token) parsePrivateKey() error {
236 block, _ := pem.Decode(t.Key)
237 if block == nil {
238 return ErrInvalidKey
239 }
240 parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
241 if err != nil {
242 parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
243 if err != nil {
244 return err
245 }
246 }
247 var ok bool
248 t.pKey, ok = parsedKey.(*rsa.PrivateKey)
249 if !ok {
250 return ErrInvalidKey
251 }
252 return nil
253 }
254
255 // Assert obtains an *oauth.Token from the remote server by encoding and sending
256 // a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
257 // refreshed (no refresh_token is returned with the response). Once this token
258 // expires call this method again to get a fresh one.
259 func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
260 var o *oauth.Token
261 u, v, err := t.buildRequest()
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) {
276 v := url.Values{}
277 j, err := t.Encode()
278 if err != nil {
279 return t.ClaimSet.Aud, v, err
280 }
281 v.Set("grant_type", stdGrantType)
282 v.Set("assertion_type", t.AssertionType())
283 v.Set("assertion", j)
284 return t.ClaimSet.Aud, v, nil
285 }
286
287 // Used for decoding the response body.
288 type respBody struct {
289 Access string `json:"access_token"`
290 Type string `json:"token_type"`
291 ExpiresIn time.Duration `json:"expires_in"`
292 }
293
294 // handleResponse returns a filled in *oauth.Token given the *http.Response from
295 // a *http.Request created by buildRequest.
296 func handleResponse(r *http.Response) (*oauth.Token, error) {
297 o := &oauth.Token{}
298 defer r.Body.Close()
299 if r.StatusCode != 200 {
300 return o, errors.New("invalid response: " + r.Status)
301 }
302 b := &respBody{}
303 err := json.NewDecoder(r.Body).Decode(b)
304 if err != nil {
305 return o, err
306 }
307 o.AccessToken = b.Access
308 o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
309 return o, nil
310 }
OLDNEW
« no previous file with comments | « no previous file | oauth/jwt/jwt_test.go » ('j') | no next file with comments »

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