git.iko.soy/typeable/oauth2-proxy
on v3.2.0 help sign in
128 lines 3.7 kB history raw
1package cookie
3import (
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"
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
21// Validate ensures a cookie is properly signed
22func 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
52// SignedValue returns a cookie that is signed and can later be checked with Validate
53func 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
61func 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)
71func 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
82// Cipher provides methods to encrypt and decrypt cookie values
83type Cipher struct {
84 cipher.Block
87// NewCipher returns a new aes Cipher for encrypting cookie values
88func 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
96// Encrypt a value for use in a cookie
97func (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 }
104 stream := cipher.NewCFBEncrypter(c.Block, iv)
105 stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(value))
106 return base64.StdEncoding.EncodeToString(ciphertext), nil
109// Decrypt a value from a cookie to it's original string
110func (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 }
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 }
122 iv := encrypted[:aes.BlockSize]
123 encrypted = encrypted[aes.BlockSize:]
124 stream := cipher.NewCFBDecrypter(c.Block, iv)
125 stream.XORKeyStream(encrypted, encrypted)
127 return string(encrypted), nil