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  }