github.com/pusher/oauth2_proxy@v3.2.0+incompatible/cookie/cookies.go (about) 1 package cookie 2 3 import ( 4 "crypto/aes" 5 "crypto/cipher" 6 "crypto/hmac" 7 "crypto/rand" 8 "crypto/sha1" 9 "encoding/base64" 10 "fmt" 11 "io" 12 "net/http" 13 "strconv" 14 "strings" 15 "time" 16 ) 17 18 // cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set. 19 // additionally, the 'value' is encrypted so it's opaque to the browser 20 21 // Validate ensures a cookie is properly signed 22 func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value string, t time.Time, ok bool) { 23 // value, timestamp, sig 24 parts := strings.Split(cookie.Value, "|") 25 if len(parts) != 3 { 26 return 27 } 28 sig := cookieSignature(seed, cookie.Name, parts[0], parts[1]) 29 if checkHmac(parts[2], sig) { 30 ts, err := strconv.Atoi(parts[1]) 31 if err != nil { 32 return 33 } 34 // The expiration timestamp set when the cookie was created 35 // isn't sent back by the browser. Hence, we check whether the 36 // creation timestamp stored in the cookie falls within the 37 // window defined by (Now()-expiration, Now()]. 38 t = time.Unix(int64(ts), 0) 39 if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) { 40 // it's a valid cookie. now get the contents 41 rawValue, err := base64.URLEncoding.DecodeString(parts[0]) 42 if err == nil { 43 value = string(rawValue) 44 ok = true 45 return 46 } 47 } 48 } 49 return 50 } 51 52 // SignedValue returns a cookie that is signed and can later be checked with Validate 53 func SignedValue(seed string, key string, value string, now time.Time) string { 54 encodedValue := base64.URLEncoding.EncodeToString([]byte(value)) 55 timeStr := fmt.Sprintf("%d", now.Unix()) 56 sig := cookieSignature(seed, key, encodedValue, timeStr) 57 cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig) 58 return cookieVal 59 } 60 61 func cookieSignature(args ...string) string { 62 h := hmac.New(sha1.New, []byte(args[0])) 63 for _, arg := range args[1:] { 64 h.Write([]byte(arg)) 65 } 66 var b []byte 67 b = h.Sum(b) 68 return base64.URLEncoding.EncodeToString(b) 69 } 70 71 func checkHmac(input, expected string) bool { 72 inputMAC, err1 := base64.URLEncoding.DecodeString(input) 73 if err1 == nil { 74 expectedMAC, err2 := base64.URLEncoding.DecodeString(expected) 75 if err2 == nil { 76 return hmac.Equal(inputMAC, expectedMAC) 77 } 78 } 79 return false 80 } 81 82 // Cipher provides methods to encrypt and decrypt cookie values 83 type Cipher struct { 84 cipher.Block 85 } 86 87 // NewCipher returns a new aes Cipher for encrypting cookie values 88 func NewCipher(secret []byte) (*Cipher, error) { 89 c, err := aes.NewCipher(secret) 90 if err != nil { 91 return nil, err 92 } 93 return &Cipher{Block: c}, err 94 } 95 96 // Encrypt a value for use in a cookie 97 func (c *Cipher) Encrypt(value string) (string, error) { 98 ciphertext := make([]byte, aes.BlockSize+len(value)) 99 iv := ciphertext[:aes.BlockSize] 100 if _, err := io.ReadFull(rand.Reader, iv); err != nil { 101 return "", fmt.Errorf("failed to create initialization vector %s", err) 102 } 103 104 stream := cipher.NewCFBEncrypter(c.Block, iv) 105 stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(value)) 106 return base64.StdEncoding.EncodeToString(ciphertext), nil 107 } 108 109 // Decrypt a value from a cookie to it's original string 110 func (c *Cipher) Decrypt(s string) (string, error) { 111 encrypted, err := base64.StdEncoding.DecodeString(s) 112 if err != nil { 113 return "", fmt.Errorf("failed to decrypt cookie value %s", err) 114 } 115 116 if len(encrypted) < aes.BlockSize { 117 return "", fmt.Errorf("encrypted cookie value should be "+ 118 "at least %d bytes, but is only %d bytes", 119 aes.BlockSize, len(encrypted)) 120 } 121 122 iv := encrypted[:aes.BlockSize] 123 encrypted = encrypted[aes.BlockSize:] 124 stream := cipher.NewCFBDecrypter(c.Block, iv) 125 stream.XORKeyStream(encrypted, encrypted) 126 127 return string(encrypted), nil 128 }