OLD | NEW |
(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 } |
OLD | NEW |