This repository has been archived by the owner on Jan 24, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #117 from jehiah/always_refresh_117
Google - continually use refresh token
- Loading branch information
Showing
21 changed files
with
866 additions
and
580 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package cookie | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/hmac" | ||
"crypto/rand" | ||
"crypto/sha1" | ||
"encoding/base64" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"strconv" | ||
"strings" | ||
"time" | ||
) | ||
|
||
// cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set. | ||
// additionally, the 'value' is encrypted so it's opaque to the browser | ||
|
||
// Validate ensures a cookie is properly signed | ||
func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value string, t time.Time, ok bool) { | ||
// value, timestamp, sig | ||
parts := strings.Split(cookie.Value, "|") | ||
if len(parts) != 3 { | ||
return | ||
} | ||
sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) | ||
if checkHmac(parts[2], sig) { | ||
ts, err := strconv.Atoi(parts[1]) | ||
if err != nil { | ||
return | ||
} | ||
// The expiration timestamp set when the cookie was created | ||
// isn't sent back by the browser. Hence, we check whether the | ||
// creation timestamp stored in the cookie falls within the | ||
// window defined by (Now()-expiration, Now()]. | ||
t = time.Unix(int64(ts), 0) | ||
if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) { | ||
// it's a valid cookie. now get the contents | ||
rawValue, err := base64.URLEncoding.DecodeString(parts[0]) | ||
if err == nil { | ||
value = string(rawValue) | ||
ok = true | ||
return | ||
} | ||
} | ||
} | ||
return | ||
} | ||
|
||
// SignedValue returns a cookie that is signed and can later be checked with Validate | ||
func SignedValue(seed string, key string, value string, now time.Time) string { | ||
encodedValue := base64.URLEncoding.EncodeToString([]byte(value)) | ||
timeStr := fmt.Sprintf("%d", now.Unix()) | ||
sig := cookieSignature(seed, key, encodedValue, timeStr) | ||
cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig) | ||
return cookieVal | ||
} | ||
|
||
func cookieSignature(args ...string) string { | ||
h := hmac.New(sha1.New, []byte(args[0])) | ||
for _, arg := range args[1:] { | ||
h.Write([]byte(arg)) | ||
} | ||
var b []byte | ||
b = h.Sum(b) | ||
return base64.URLEncoding.EncodeToString(b) | ||
} | ||
|
||
func checkHmac(input, expected string) bool { | ||
inputMAC, err1 := base64.URLEncoding.DecodeString(input) | ||
if err1 == nil { | ||
expectedMAC, err2 := base64.URLEncoding.DecodeString(expected) | ||
if err2 == nil { | ||
return hmac.Equal(inputMAC, expectedMAC) | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// Cipher provides methods to encrypt and decrypt cookie values | ||
type Cipher struct { | ||
cipher.Block | ||
} | ||
|
||
// NewCipher returns a new aes Cipher for encrypting cookie values | ||
func NewCipher(secret string) (*Cipher, error) { | ||
c, err := aes.NewCipher([]byte(secret)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Cipher{Block: c}, err | ||
} | ||
|
||
// Encrypt a value for use in a cookie | ||
func (c *Cipher) Encrypt(value string) (string, error) { | ||
ciphertext := make([]byte, aes.BlockSize+len(value)) | ||
iv := ciphertext[:aes.BlockSize] | ||
if _, err := io.ReadFull(rand.Reader, iv); err != nil { | ||
return "", fmt.Errorf("failed to create initialization vector %s", err) | ||
} | ||
|
||
stream := cipher.NewCFBEncrypter(c.Block, iv) | ||
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(value)) | ||
return base64.StdEncoding.EncodeToString(ciphertext), nil | ||
} | ||
|
||
// Decrypt a value from a cookie to it's original string | ||
func (c *Cipher) Decrypt(s string) (string, error) { | ||
encrypted, err := base64.StdEncoding.DecodeString(s) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to decrypt cookie value %s", err) | ||
} | ||
|
||
if len(encrypted) < aes.BlockSize { | ||
return "", fmt.Errorf("encrypted cookie value should be "+ | ||
"at least %d bytes, but is only %d bytes", | ||
aes.BlockSize, len(encrypted)) | ||
} | ||
|
||
iv := encrypted[:aes.BlockSize] | ||
encrypted = encrypted[aes.BlockSize:] | ||
stream := cipher.NewCFBDecrypter(c.Block, iv) | ||
stream.XORKeyStream(encrypted, encrypted) | ||
|
||
return string(encrypted), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package cookie | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/bmizerany/assert" | ||
) | ||
|
||
func TestEncodeAndDecodeAccessToken(t *testing.T) { | ||
const secret = "0123456789abcdefghijklmnopqrstuv" | ||
const token = "my access token" | ||
c, err := NewCipher(secret) | ||
assert.Equal(t, nil, err) | ||
|
||
encoded, err := c.Encrypt(token) | ||
assert.Equal(t, nil, err) | ||
|
||
decoded, err := c.Decrypt(encoded) | ||
assert.Equal(t, nil, err) | ||
|
||
assert.NotEqual(t, token, encoded) | ||
assert.Equal(t, token, decoded) | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.