Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit aee9b4c

Browse files
author
Pirateninjawizard
committed
implemented EncryptSignedCookie and its dependencies
1 parent 2786ed0 commit aee9b4c

File tree

2 files changed

+129
-14
lines changed

2 files changed

+129
-14
lines changed

session/session.go

+75-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"crypto/aes"
55
"crypto/cipher"
66
"crypto/hmac"
7+
"crypto/rand"
78
"crypto/sha1"
89
"encoding/base64"
910
"encoding/hex"
@@ -20,12 +21,16 @@ func generateSecret(base, salt string) []byte {
2021
return pbkdf2.Key([]byte(base), []byte(salt), keyIterNum, keySize, sha1.New)
2122
}
2223

23-
// The origin of this snippet can be found at https://gist.github.com/doitian/2a89dc9e4372e55335c9111f576b47bf
24-
func verifySign(encryptedData, sign, base, signSalt string) (bool, error) {
24+
func signData(encryptedData, base, signSalt string) []byte {
2525
signKey := generateSecret(base, signSalt)
2626
signHmac := hmac.New(sha1.New, signKey)
2727
signHmac.Write([]byte(encryptedData))
28-
verifySign := signHmac.Sum(nil)
28+
return signHmac.Sum(nil)
29+
}
30+
31+
// The origin of this snippet can be found at https://gist.github.com/doitian/2a89dc9e4372e55335c9111f576b47bf
32+
func verifySign(encryptedData, sign, base, signSalt string) (bool, error) {
33+
verifySign := signData(encryptedData, base, signSalt)
2934
signDecoded, err := hex.DecodeString(sign)
3035
if err != nil {
3136
return false, err
@@ -36,6 +41,12 @@ func verifySign(encryptedData, sign, base, signSalt string) (bool, error) {
3641
return true, nil
3742
}
3843

44+
// sign and join data with signature using "--" (needs to be url.QueryEscape'd)
45+
func signJoiner(encryptedData, base, signSalt string) string {
46+
postfix := hex.EncodeToString(signData(encryptedData, base, signSalt))
47+
return strings.Join([]string{encryptedData, postfix}, "--")
48+
}
49+
3950
func decodeCookieData(cookie []byte) (data, iv []byte, err error) {
4051
vectors := strings.SplitN(string(cookie), "--", 2)
4152

@@ -52,6 +63,13 @@ func decodeCookieData(cookie []byte) (data, iv []byte, err error) {
5263
return
5364
}
5465

66+
func encodeCookieData(data, iv []byte) (cookie []byte) {
67+
datas := base64.StdEncoding.EncodeToString(data)
68+
ivs := base64.StdEncoding.EncodeToString(iv)
69+
cookie = []byte(strings.Join([]string{datas, ivs}, "--"))
70+
return
71+
}
72+
5573
func decryptCookie(cookie []byte, secret []byte) (dd []byte, err error) {
5674
data, iv, err := decodeCookieData(cookie)
5775

@@ -67,6 +85,60 @@ func decryptCookie(cookie []byte, secret []byte) (dd []byte, err error) {
6785
return
6886
}
6987

88+
// padSession implements PKCS#7 padding for the plaintext
89+
// https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7
90+
func padSession(session []byte, blockSize int) []byte {
91+
sesslen := len(session)
92+
padsize := blockSize - (sesslen % blockSize)
93+
if padsize == blockSize {
94+
return session
95+
}
96+
newlen := sesslen + padsize
97+
padbyte := byte(padsize)
98+
padded := make([]byte, newlen)
99+
copy(padded, session)
100+
for i := sesslen; i < newlen; i++ {
101+
padded[i] = padbyte
102+
}
103+
104+
return padded
105+
}
106+
107+
func encryptCookie(dd, secret []byte) (cookie []byte, err error) {
108+
c, err := aes.NewCipher(secret[:32])
109+
if err != nil {
110+
return
111+
}
112+
padded := padSession(dd, c.BlockSize())
113+
iv := make([]byte, c.BlockSize())
114+
// rails uses a random iv, so this should be fine: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/message_encryptor.rb#L172
115+
_, err = rand.Read(iv)
116+
if err != nil {
117+
return
118+
}
119+
120+
cfb := cipher.NewCBCEncrypter(c, iv)
121+
data := make([]byte, len(padded))
122+
cfb.CryptBlocks(data, padded)
123+
124+
cookie = encodeCookieData(data, iv)
125+
126+
return
127+
}
128+
129+
// EncryptSignedCookie encrypts and signs session to produce a cookie that rails can read
130+
func EncryptSignedCookie(session []byte, secretKeyBase, salt, signSalt string) (signedCookie string, err error) {
131+
data, err := encryptCookie(session, generateSecret(secretKeyBase, salt))
132+
if err != nil {
133+
return
134+
}
135+
136+
datastr := base64.StdEncoding.EncodeToString(data)
137+
cookie := signJoiner(datastr, secretKeyBase, signSalt)
138+
signedCookie = url.QueryEscape(cookie)
139+
return
140+
}
141+
70142
func DecryptSignedCookie(signedCookie, secretKeyBase, salt, signSalt string) (session []byte, err error) {
71143
cookie, err := url.QueryUnescape(signedCookie)
72144
if err != nil {

session/session_test.go

+54-11
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
package session
22

33
import (
4-
"encoding/json"
4+
"bytes"
55
"net/url"
66
"strings"
77
"testing"
88
)
99

1010
const (
1111
secretKeyBase = "fe98c394d54eeae9edff39c1934b156607e4376188463d397d460eef9585cf15c0dd23f353877552d1c9b0565a03b7fdeadfb33907c6d582eb02319a7409610b"
12-
salt = "encrypted cookie"
13-
signSalt = "signed encrypted cookie"
14-
1512
// The cookie's original content is:
1613
// map[flash:map[discard:[] flashes:map[notice:Welcome! You have signed up successfully.]] session_id:b85897340bfedc7e03b7e9479c271439 _csrf_token:dTDcQiGuEE8n6KUQmXNhIoXsLQJlqrBPUAsspGMpkdg= warden.user.user.key:[[1] $2a$11$6omJ7/e3Ni7Pl7jZbCdDBu]]
1714
signedCookie = "RkpiOStFLzExVm42aXZiMFZWaDB3c09rbEE4aTUvcEg5Q1VnaTNDOTBwMTdSUGFsdjZqbWZpQmV3eXhQbEJieE1EYXZCQXNGNFhKREI5aUx0aXVFZE1vaXQzSTdtYzc5S1NmeXBEZG93Mm1PQmQ2RVMvdjRqbTdsTW1qTjcxRTZFSVpCZFBUcTByN0ZYQmhWWVZPVE45RUsyS2NRcEV5QkdsajRUL3FGYjNmdUZrYmZ5TVZxSlpucllOaXlTN0pZZG85eHlMNEN0MVdYayttdE8wNTBTSElDYTRqditGMmpoL09hcDhkTFZ0dngyM244aG53aWNLNWRvVTN3K2dpUWd0eGttRXZUdGx2TGJHS0xlN0hKWFI2aVhuQlE4Y3NvYWx1QTZvcDRkbDJZdjl4NGJ1b1B1WW9QdXdEOVpzcCtBR1BCVDkxZkNSVENJZkVqMkgzR3pxQ1lVVEJmQlBYK0ZIQWJ5WHRpOC84PS0taDluekdrZE1LbzVrZDVlMHFSSzNjdz09--5f676b46cb0671630fd33bfec08b6fbf3f858c6a"
15+
salt = "encrypted cookie"
16+
signSalt = "signed encrypted cookie"
17+
18+
// The cookie's original content is:
19+
plainjson = `{"session_id":"b85897340bfedc7e03b7e9479c271439","_csrf_token":"dTDcQiGuEE8n6KUQmXNhIoXsLQJlqrBPUAsspGMpkdg=","warden.user.user.key":[[1],"$2a$11$6omJ7/e3Ni7Pl7jZbCdDBu"],"flash":{"discard":[],"flashes":{"notice":"Welcome! You have signed up successfully."}}}`
1820
)
1921

22+
type sessionObj struct {
23+
SessionID string `json:"session_id"`
24+
CSRF string `json:"_csrf_token"`
25+
}
26+
2027
func TestVerifySign(t *testing.T) {
2128
cookie, _ := url.QueryUnescape(signedCookie)
2229
vectors := strings.SplitN(cookie, "--", 2)
@@ -36,16 +43,52 @@ func TestVerifySign(t *testing.T) {
3643
}
3744
}
3845

39-
func TestDecryptSignedCookie(t *testing.T) {
40-
cookieData, err := DecryptSignedCookie(signedCookie, secretKeyBase, salt, signSalt)
46+
func TestSignJoiner(t *testing.T) {
47+
encryptedData := "RkpiOStFLzExVm42aXZiMFZWaDB3c09rbEE4aTUvcEg5Q1VnaTNDOTBwMTdSUGFsdjZqbWZpQmV3eXhQbEJieE1EYXZCQXNGNFhKREI5aUx0aXVFZE1vaXQzSTdtYzc5S1NmeXBEZG93Mm1PQmQ2RVMvdjRqbTdsTW1qTjcxRTZFSVpCZFBUcTByN0ZYQmhWWVZPVE45RUsyS2NRcEV5QkdsajRUL3FGYjNmdUZrYmZ5TVZxSlpucllOaXlTN0pZZG85eHlMNEN0MVdYayttdE8wNTBTSElDYTRqditGMmpoL09hcDhkTFZ0dngyM244aG53aWNLNWRvVTN3K2dpUWd0eGttRXZUdGx2TGJHS0xlN0hKWFI2aVhuQlE4Y3NvYWx1QTZvcDRkbDJZdjl4NGJ1b1B1WW9QdXdEOVpzcCtBR1BCVDkxZkNSVENJZkVqMkgzR3pxQ1lVVEJmQlBYK0ZIQWJ5WHRpOC84PS0taDluekdrZE1LbzVrZDVlMHFSSzNjdz09"
48+
if want, got := signedCookie, url.QueryEscape(signJoiner(encryptedData, secretKeyBase, signSalt)); want != got {
49+
t.Errorf("expected: %q\ngot: %q", want, got)
50+
}
51+
}
52+
53+
func TestEncryptCookie(t *testing.T) {
54+
// get plaintext
55+
plaintext, _ := DecryptSignedCookie(signedCookie, secretKeyBase, salt, signSalt)
56+
ciphertext, _ := encryptCookie(plaintext, generateSecret(secretKeyBase, salt))
57+
plaintext2, _ := decryptCookie(ciphertext, generateSecret(secretKeyBase, salt))
58+
if !bytes.Equal(plaintext, plaintext2) {
59+
t.Errorf("ciphertext output by encryptCookie cannot be decrypted by decryptCookie")
60+
}
61+
}
62+
63+
func TestEncryptSignedCookie(t *testing.T) {
64+
want := make([]byte, len(plainjson)+13)
65+
copy(want, plainjson)
66+
for i := len(plainjson); i < len(plainjson)+13; i++ {
67+
want[i] = 13
68+
}
69+
70+
ciphertext, _ := EncryptSignedCookie([]byte(plainjson), secretKeyBase, salt, signSalt)
71+
got, err := DecryptSignedCookie(ciphertext, secretKeyBase, salt, signSalt)
4172
if err != nil {
42-
t.Errorf("DecryptSignedCookie test failure: %v", err)
73+
t.Errorf("got error from decrypting ciphertext: %v", err)
74+
}
75+
if !bytes.Equal(want, got) {
76+
t.Errorf("decrypted ciphertext %q does not match plaintext %q", string(got), string(want))
4377
}
44-
var jsonData map[string]interface{}
45-
if err := json.Unmarshal(cookieData, &jsonData); err != nil {
78+
}
79+
80+
func TestDecryptSignedCookie(t *testing.T) {
81+
want := make([]byte, len(plainjson)+13)
82+
copy(want, plainjson)
83+
for i := len(plainjson); i < len(plainjson)+13; i++ {
84+
want[i] = 13
85+
}
86+
87+
got, err := DecryptSignedCookie(signedCookie, secretKeyBase, salt, signSalt)
88+
if err != nil {
4689
t.Errorf("DecryptSignedCookie test failure: %v", err)
4790
}
48-
if jsonData["session_id"] != "b85897340bfedc7e03b7e9479c271439" {
49-
t.Error("DecryptSignedCookie get wrong values after deserialization")
91+
if !bytes.Equal(want, got) {
92+
t.Errorf("decrypted ciphertext %q does not match plaintext %q", string(got), string(want))
5093
}
5194
}

0 commit comments

Comments
 (0)