202 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			202 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Beego (http://beego.me/)
 | 
						|
 | 
						|
// @description beego is an open-source, high-performance web framework for the Go programming language.
 | 
						|
 | 
						|
// @link        http://github.com/astaxie/beego for the canonical source repository
 | 
						|
 | 
						|
// @license     http://github.com/astaxie/beego/blob/master/LICENSE
 | 
						|
 | 
						|
// @authors     astaxie
 | 
						|
 | 
						|
package session
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"crypto/cipher"
 | 
						|
	"crypto/hmac"
 | 
						|
	"crypto/rand"
 | 
						|
	"crypto/sha1"
 | 
						|
	"crypto/subtle"
 | 
						|
	"encoding/base64"
 | 
						|
	"encoding/gob"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"strconv"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/astaxie/beego/utils"
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	gob.Register([]interface{}{})
 | 
						|
	gob.Register(map[int]interface{}{})
 | 
						|
	gob.Register(map[string]interface{}{})
 | 
						|
	gob.Register(map[interface{}]interface{}{})
 | 
						|
	gob.Register(map[string]string{})
 | 
						|
	gob.Register(map[int]string{})
 | 
						|
	gob.Register(map[int]int{})
 | 
						|
	gob.Register(map[int]int64{})
 | 
						|
}
 | 
						|
 | 
						|
func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
 | 
						|
	for _, v := range obj {
 | 
						|
		gob.Register(v)
 | 
						|
	}
 | 
						|
	buf := bytes.NewBuffer(nil)
 | 
						|
	enc := gob.NewEncoder(buf)
 | 
						|
	err := enc.Encode(obj)
 | 
						|
	if err != nil {
 | 
						|
		return []byte(""), err
 | 
						|
	}
 | 
						|
	return buf.Bytes(), nil
 | 
						|
}
 | 
						|
 | 
						|
func DecodeGob(encoded []byte) (map[interface{}]interface{}, error) {
 | 
						|
	buf := bytes.NewBuffer(encoded)
 | 
						|
	dec := gob.NewDecoder(buf)
 | 
						|
	var out map[interface{}]interface{}
 | 
						|
	err := dec.Decode(&out)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return out, nil
 | 
						|
}
 | 
						|
 | 
						|
// generateRandomKey creates a random key with the given strength.
 | 
						|
func generateRandomKey(strength int) []byte {
 | 
						|
	k := make([]byte, strength)
 | 
						|
	if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil {
 | 
						|
		return utils.RandomCreateBytes(strength)
 | 
						|
	}
 | 
						|
	return k
 | 
						|
}
 | 
						|
 | 
						|
// Encryption -----------------------------------------------------------------
 | 
						|
 | 
						|
// encrypt encrypts a value using the given block in counter mode.
 | 
						|
//
 | 
						|
// A random initialization vector (http://goo.gl/zF67k) with the length of the
 | 
						|
// block size is prepended to the resulting ciphertext.
 | 
						|
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
 | 
						|
	iv := generateRandomKey(block.BlockSize())
 | 
						|
	if iv == nil {
 | 
						|
		return nil, errors.New("encrypt: failed to generate random iv")
 | 
						|
	}
 | 
						|
	// Encrypt it.
 | 
						|
	stream := cipher.NewCTR(block, iv)
 | 
						|
	stream.XORKeyStream(value, value)
 | 
						|
	// Return iv + ciphertext.
 | 
						|
	return append(iv, value...), nil
 | 
						|
}
 | 
						|
 | 
						|
// decrypt decrypts a value using the given block in counter mode.
 | 
						|
//
 | 
						|
// The value to be decrypted must be prepended by a initialization vector
 | 
						|
// (http://goo.gl/zF67k) with the length of the block size.
 | 
						|
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
 | 
						|
	size := block.BlockSize()
 | 
						|
	if len(value) > size {
 | 
						|
		// Extract iv.
 | 
						|
		iv := value[:size]
 | 
						|
		// Extract ciphertext.
 | 
						|
		value = value[size:]
 | 
						|
		// Decrypt it.
 | 
						|
		stream := cipher.NewCTR(block, iv)
 | 
						|
		stream.XORKeyStream(value, value)
 | 
						|
		return value, nil
 | 
						|
	}
 | 
						|
	return nil, errors.New("decrypt: the value could not be decrypted")
 | 
						|
}
 | 
						|
 | 
						|
func encodeCookie(block cipher.Block, hashKey, name string, value map[interface{}]interface{}) (string, error) {
 | 
						|
	var err error
 | 
						|
	var b []byte
 | 
						|
	// 1. EncodeGob.
 | 
						|
	if b, err = EncodeGob(value); err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	// 2. Encrypt (optional).
 | 
						|
	if b, err = encrypt(block, b); err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	b = encode(b)
 | 
						|
	// 3. Create MAC for "name|date|value". Extra pipe to be used later.
 | 
						|
	b = []byte(fmt.Sprintf("%s|%d|%s|", name, time.Now().UTC().Unix(), b))
 | 
						|
	h := hmac.New(sha1.New, []byte(hashKey))
 | 
						|
	h.Write(b)
 | 
						|
	sig := h.Sum(nil)
 | 
						|
	// Append mac, remove name.
 | 
						|
	b = append(b, sig...)[len(name)+1:]
 | 
						|
	// 4. Encode to base64.
 | 
						|
	b = encode(b)
 | 
						|
	// Done.
 | 
						|
	return string(b), nil
 | 
						|
}
 | 
						|
 | 
						|
func decodeCookie(block cipher.Block, hashKey, name, value string, gcmaxlifetime int64) (map[interface{}]interface{}, error) {
 | 
						|
	// 1. Decode from base64.
 | 
						|
	b, err := decode([]byte(value))
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// 2. Verify MAC. Value is "date|value|mac".
 | 
						|
	parts := bytes.SplitN(b, []byte("|"), 3)
 | 
						|
	if len(parts) != 3 {
 | 
						|
		return nil, errors.New("Decode: invalid value %v")
 | 
						|
	}
 | 
						|
 | 
						|
	b = append([]byte(name+"|"), b[:len(b)-len(parts[2])]...)
 | 
						|
	h := hmac.New(sha1.New, []byte(hashKey))
 | 
						|
	h.Write(b)
 | 
						|
	sig := h.Sum(nil)
 | 
						|
	if len(sig) != len(parts[2]) || subtle.ConstantTimeCompare(sig, parts[2]) != 1 {
 | 
						|
		return nil, errors.New("Decode: the value is not valid")
 | 
						|
	}
 | 
						|
	// 3. Verify date ranges.
 | 
						|
	var t1 int64
 | 
						|
	if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
 | 
						|
		return nil, errors.New("Decode: invalid timestamp")
 | 
						|
	}
 | 
						|
	t2 := time.Now().UTC().Unix()
 | 
						|
	if t1 > t2 {
 | 
						|
		return nil, errors.New("Decode: timestamp is too new")
 | 
						|
	}
 | 
						|
	if t1 < t2-gcmaxlifetime {
 | 
						|
		return nil, errors.New("Decode: expired timestamp")
 | 
						|
	}
 | 
						|
	// 4. Decrypt (optional).
 | 
						|
	b, err = decode(parts[1])
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	if b, err = decrypt(block, b); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	// 5. DecodeGob.
 | 
						|
	if dst, err := DecodeGob(b); err != nil {
 | 
						|
		return nil, err
 | 
						|
	} else {
 | 
						|
		return dst, nil
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// Encoding -------------------------------------------------------------------
 | 
						|
 | 
						|
// encode encodes a value using base64.
 | 
						|
func encode(value []byte) []byte {
 | 
						|
	encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
 | 
						|
	base64.URLEncoding.Encode(encoded, value)
 | 
						|
	return encoded
 | 
						|
}
 | 
						|
 | 
						|
// decode decodes a cookie using base64.
 | 
						|
func decode(value []byte) ([]byte, error) {
 | 
						|
	decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
 | 
						|
	b, err := base64.URLEncoding.Decode(decoded, value)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return decoded[:b], nil
 | 
						|
}
 |