Skip to content

Commit

Permalink
Merge pull request #13 from akues-an/main
Browse files Browse the repository at this point in the history
Add support for Rails 5.2+ cookies, fix Django bug
  • Loading branch information
iangcarroll authored Oct 12, 2024
2 parents 7e8826e + 3ac317c commit 0b4ace0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 5 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
module github.com/iangcarroll/cookiemonster

go 1.17

replace github.com/evanw/esbuild => github.com/matthewmueller/esbuild v0.5.14-0.20200629201743-71029dfd2011

require github.com/xdg-go/pbkdf2 v1.0.0 // indirect
9 changes: 7 additions & 2 deletions pkg/monster/django.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,15 @@ func djangoDecode(c *Cookie) bool {
}

parsedData.decodedSignature = decodedSignature
parsedData.toBeSigned = []byte(parsedData.data + djangoSeparator + parsedData.timestamp)

// If compressed, we need to add back on the '.'
toBeSignedPrefix := ""
if parsedData.compressed {
toBeSignedPrefix = "."
}
parsedData.toBeSigned = []byte(toBeSignedPrefix + parsedData.data + djangoSeparator + parsedData.timestamp)
parsedData.parsed = true
c.wasDecodedBy(djangoDecoder, &parsedData)

return true
}

Expand Down
94 changes: 91 additions & 3 deletions pkg/monster/rack.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ package monster

import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"net/url"
"strings"

"github.com/xdg-go/pbkdf2"

Check failure on line 15 in pkg/monster/rack.go

View workflow job for this annotation

GitHub Actions / build

missing go.sum entry for module providing package github.com/xdg-go/pbkdf2 (imported by github.com/iangcarroll/cookiemonster/pkg/monster); to add:
)

type rackParsedData struct {
data string
decodedData []byte
iv string
decodedIv []byte
signature string
decodedSignature []byte
algorithm string
Expand All @@ -21,6 +32,10 @@ func (d *rackParsedData) String() string {
return "Unparsed data"
}

if d.iv != "" {
return fmt.Sprintf("Data: %s\nIV: %s\nSignature: %s\n Algorithm: %s\n", d.data, d.iv, d.signature, d.algorithm)
}

return fmt.Sprintf("Data: %s\nSignature: %s\nAlgorithm: %s\n", d.data, d.signature, d.algorithm)
}

Expand All @@ -46,18 +61,71 @@ func rackDecode(c *Cookie) bool {
}

rawData := c.raw
var parsedData rackParsedData

// Break the cookie out into the session data and signature.
components := strings.Split(rawData, rackSeparator)
if len(components) != 2 {
if len(components) == 2 {
return rackDecodeSig(c, components)
}
if len(components) == 3 {
// In Rails 5.2+, encrypted is the default
return rackDecodeAead(c, components)
}
return false
}

func rackDecodeAead(c *Cookie, components []string) bool {
var parsedData rackParsedData

parsedData.data = components[0]
parsedData.iv = components[1]
parsedData.signature = components[2]

// In the AEAD mode, the two IV and auth tag (= signature) are
// base64 encoded and URL encoded
unescapedIv, err := url.QueryUnescape(parsedData.iv)
if err != nil {
return false
}
rawIv, err := base64.StdEncoding.DecodeString(unescapedIv)
if err != nil {
return false
}
parsedData.decodedIv = rawIv

unescapedSignature, err := url.QueryUnescape(parsedData.signature)
if err != nil {
return false
}
rawSignature, err := base64.StdEncoding.DecodeString(unescapedSignature)
if err != nil {
return false
}
parsedData.decodedSignature = rawSignature

unescapedData, err := url.QueryUnescape(parsedData.data)
if err != nil {
return false
}
rawData, err := base64.StdEncoding.DecodeString(unescapedData)
if err != nil {
return false
}
parsedData.decodedData = rawData

parsedData.algorithm = "aes-256-gcm"
c.wasDecodedBy(rackDecoder, &parsedData)
return true

}

func rackDecodeSig(c *Cookie, components []string) bool {
var parsedData rackParsedData

parsedData.data = components[0]
parsedData.signature = components[1]

// Flask encodes the signature with URL-safe base64
// Rack encodes the signature with URL-safe base64
// without padding, so we must use `RawURLEncoding`.
decodedSignature, err := hex.DecodeString(parsedData.signature)
if err != nil {
Expand Down Expand Up @@ -109,6 +177,26 @@ func rackUnsign(c *Cookie, secret []byte) bool {

// Compare this signature to the one in the `Cookie`.
return bytes.Compare(parsedData.decodedSignature, computedSignature) == 0
case "aes-256-gcm":
// Rails 6 AES-GCM
aesSecret := pbkdf2.Key(secret, []byte("authenticated encrypted cookie"), 1000, 32, sha256.New)
block, err := aes.NewCipher(aesSecret)
if err != nil {
return false
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return false
}

// In go, the auth tag is appended to the data
ciphertext := append(parsedData.decodedData, parsedData.decodedSignature...)

plaintext, err := aesGCM.Open(nil, parsedData.decodedIv, ciphertext, nil)
if err != nil {
return false
}
return json.Valid(plaintext)
default:
panic("unknown algorithm")
}
Expand Down

0 comments on commit 0b4ace0

Please sign in to comment.