// 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. // iss := "XXXXXXXXXXXX@developer.gserviceaccount.com" // scope := "https://www.googleapis.com/auth/devstorage.read_only" // t := jwt.NewToken(iss, scope, pemKeyBytes) // // // 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"` Sub string `json:"sub,omitempty"` // Add support for googleapi delegation support // 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 -nocerts -passin pass:notasecret -nodes -out // // 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 t.ClaimSet.setTimes(time.Now()) 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 } // Transport implements http.RoundTripper. When configured with a valid // JWT and OAuth tokens it can be used to make authenticated HTTP requests. // // t := &jwt.Transport{jwtToken, oauthToken} // r, _, err := t.Client().Get("http://example.org/url/requiring/auth") // // It will automatically refresh the OAuth token if it can, updating in place. type Transport struct { JWTToken *Token OAuthToken *oauth.Token // Transport is the HTTP transport to use when making requests. // It will default to http.DefaultTransport if nil. Transport http.RoundTripper } // Creates a new authenticated transport. func NewTransport(token *Token) (*Transport, error) { oa, err := token.Assert(new(http.Client)) if err != nil { return nil, err } return &Transport{ JWTToken: token, OAuthToken: oa, }, nil } // Client returns an *http.Client that makes OAuth-authenticated requests. func (t *Transport) Client() *http.Client { return &http.Client{Transport: t} } // Fetches the internal transport. func (t *Transport) transport() http.RoundTripper { if t.Transport != nil { return t.Transport } return http.DefaultTransport } // RoundTrip executes a single HTTP transaction using the Transport's // OAuthToken as authorization headers. // // This method will attempt to renew the token if it has expired and may return // an error related to that token renewal before attempting the client request. // If the token cannot be renewed a non-nil os.Error value will be returned. // If the token is invalid callers should expect HTTP-level errors, // as indicated by the Response's StatusCode. func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { // Sanity check the two tokens if t.JWTToken == nil { return nil, fmt.Errorf("no JWT token supplied") } if t.OAuthToken == nil { return nil, fmt.Errorf("no OAuth token supplied") } // Refresh the OAuth token if it has expired if t.OAuthToken.Expired() { if oa, err := t.JWTToken.Assert(new(http.Client)); err != nil { return nil, err } else { t.OAuthToken = oa } } // To set the Authorization header, we must make a copy of the Request // so that we don't modify the Request we were given. // This is required by the specification of http.RoundTripper. req = cloneRequest(req) req.Header.Set("Authorization", "Bearer "+t.OAuthToken.AccessToken) // Make the HTTP request. return t.transport().RoundTrip(req) } // cloneRequest returns a clone of the provided *http.Request. // The clone is a shallow copy of the struct and its Header map. func cloneRequest(r *http.Request) *http.Request { // shallow copy of the struct r2 := new(http.Request) *r2 = *r // deep copy of the Header r2.Header = make(http.Header) for k, s := range r.Header { r2.Header[k] = s } return r2 }