Add supprt for new Rails 5.2 aes-256-gcm cookies
This commit is contained in:
171
rails/auth.go
Normal file
171
rails/auth.go
Normal file
@ -0,0 +1,171 @@
|
||||
package rails
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/adjust/gorails/marshal"
|
||||
)
|
||||
|
||||
const (
|
||||
salt = "encrypted cookie"
|
||||
signSalt = "signed encrypted cookie"
|
||||
authSalt = "authenticated encrypted cookie"
|
||||
railsCipher = "aes-256-cbc"
|
||||
railsCipher52 = "aes-256-gcm"
|
||||
)
|
||||
|
||||
var (
|
||||
errSessionData = errors.New("error decoding session data")
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Cipher string
|
||||
Secret string
|
||||
Salt string
|
||||
SignSalt string
|
||||
AuthSalt string
|
||||
}
|
||||
|
||||
func NewAuth(version, secret string) (*Auth, error) {
|
||||
ra := &Auth{
|
||||
Secret: secret,
|
||||
Salt: salt,
|
||||
SignSalt: signSalt,
|
||||
AuthSalt: authSalt,
|
||||
}
|
||||
|
||||
ver, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rails auth: %s", err)
|
||||
}
|
||||
|
||||
gt52, err := semver.NewConstraint(">= 5.2")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rails auth: %s", err)
|
||||
}
|
||||
|
||||
if gt52.Check(ver) {
|
||||
ra.Cipher = railsCipher52
|
||||
} else {
|
||||
ra.Cipher = railsCipher
|
||||
}
|
||||
|
||||
return ra, nil
|
||||
}
|
||||
|
||||
func (ra *Auth) ParseCookie(cookie string) (userID string, err error) {
|
||||
var dcookie []byte
|
||||
|
||||
switch ra.Cipher {
|
||||
case railsCipher:
|
||||
dcookie, err = parseCookie(cookie, ra.Secret, ra.Salt, ra.SignSalt)
|
||||
|
||||
case railsCipher52:
|
||||
dcookie, err = parseCookie52(cookie, ra.Secret, ra.AuthSalt)
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unknown rails cookie cipher '%s'", ra.Cipher)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if dcookie[0] != '{' {
|
||||
userID, err = getUserId4(dcookie)
|
||||
} else {
|
||||
userID, err = getUserId(dcookie)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ParseCookie(cookie string) (string, error) {
|
||||
if cookie[0] != '{' {
|
||||
return getUserId4([]byte(cookie))
|
||||
}
|
||||
|
||||
return getUserId([]byte(cookie))
|
||||
}
|
||||
|
||||
func getUserId(data []byte) (userID string, err error) {
|
||||
var sessionData map[string]interface{}
|
||||
|
||||
err = json.Unmarshal(data, &sessionData)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userKey, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errors.New("key 'warden.user.user.key' not found in session data")
|
||||
}
|
||||
|
||||
items, ok := userKey.([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
if len(items) != 2 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uids, ok := items[0].([]interface{})
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, ok := uids[0].(float64)
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", int64(uid))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getUserId4(data []byte) (userID string, err error) {
|
||||
sessionData, err := marshal.CreateMarshalledObject(data).GetAsMap()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
wardenData, ok := sessionData["warden.user.user.key"]
|
||||
if !ok {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
wardenUserKey, err := wardenData.GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(wardenUserKey) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
userData, err := wardenUserKey[0].GetAsArray()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(userData) < 1 {
|
||||
err = errSessionData
|
||||
return
|
||||
}
|
||||
|
||||
uid, err := userData[0].GetAsInteger()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
userID = fmt.Sprintf("%d", uid)
|
||||
|
||||
return
|
||||
}
|
79
rails/auth_test.go
Normal file
79
rails/auth_test.go
Normal file
@ -0,0 +1,79 @@
|
||||
package rails
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRailsEncryptedSession1(t *testing.T) {
|
||||
cookie := "dDdjMW5jYUNYaFpBT1BSdFgwQkk4ZWNlT214L1FnM0pyZzZ1d21nSnVTTm9zS0ljN000S1JmT3cxcTNtRld2Ny0tQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT09--75d8323b0f0e41cf4d5aabee1b229b1be76b83b6"
|
||||
|
||||
secret := "development_secret"
|
||||
|
||||
ra := Auth{
|
||||
Cipher: railsCipher,
|
||||
Secret: secret,
|
||||
Salt: salt,
|
||||
SignSalt: signSalt,
|
||||
}
|
||||
|
||||
userID, err := ra.ParseCookie(cookie)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID != "1" {
|
||||
t.Errorf("Expecting userID 1 got %s", userID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailsEncryptedSession52(t *testing.T) {
|
||||
cookie :=
|
||||
"fZy1lt%2FIuXh2cpQgy3wWjbvabh1AqJX%2Bt6qO4D95DOZIpDhMyK2HqPFeNoaBtrXCUa9%2BDQuvbs1GX6tuccEAp14QPLNhm0PPJS5U1pRHqPLWaqT%2BBPYP%2BY9bo677komm9CPuOCOqBKf7rv3%2F4ptLmVO7iefB%2FP2ZlkV1848Johv5q%2B5PGyMxII2BEQnBdS3Petw6lRu741Bquc8z9VofC3t4%2F%2BLxVz%2BvBbTg--VL0MorYITXB8Dj3W--0yr0sr6pRU%2FwlYMQ%2BpEifA%3D%3D"
|
||||
|
||||
secret := "0a248500a64c01184edb4d7ad3a805488f8097ac761b76aaa6c17c01dcb7af03a2f18ba61b2868134b9c7b79a122bc0dadff4367414a2d173297bfea92be5566"
|
||||
|
||||
ra := Auth{
|
||||
Cipher: railsCipher52,
|
||||
Secret: secret,
|
||||
AuthSalt: authSalt,
|
||||
}
|
||||
|
||||
userID, err := ra.ParseCookie(cookie)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID != "2" {
|
||||
t.Errorf("Expecting userID 2 got %s", userID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailsJsonSession(t *testing.T) {
|
||||
sessionData := `{"warden.user.user.key":[[1],"secret"]}`
|
||||
|
||||
userID, err := getUserId([]byte(sessionData))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID != "1" {
|
||||
t.Errorf("Expecting userID 1 got %s", userID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRailsMarshaledSession(t *testing.T) {
|
||||
sessionData := "\x04\b{\bI\"\x15member_return_to\x06:\x06ETI\"\x06/\x06;\x00TI\"\x19warden.user.user.key\x06;\x00T[\a[\x06i\aI\"\"$2a$11$6SgXdvO9hld82kQAvpEY3e\x06;\x00TI\"\x10_csrf_token\x06;\x00FI\"17lqwj1UsTTgbXBQKH4ipCNW32uLusvfSPds1txppMec=\x06;\x00F"
|
||||
|
||||
userID, err := getUserId4([]byte(sessionData))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID != "2" {
|
||||
t.Errorf("Expecting userID 2 got %s", userID)
|
||||
}
|
||||
}
|
62
rails/cookie.go
Normal file
62
rails/cookie.go
Normal file
@ -0,0 +1,62 @@
|
||||
package rails
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/adjust/gorails/session"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
func parseCookie(cookie, secretKeyBase, salt, signSalt string) ([]byte, error) {
|
||||
return session.DecryptSignedCookie(
|
||||
cookie,
|
||||
secretKeyBase,
|
||||
salt,
|
||||
signSalt)
|
||||
}
|
||||
|
||||
// {"session_id":"a71d6ffcd4ed5572ea2097f569eb95ef","warden.user.user.key":[[2],"$2a$11$q9Br7m4wJxQvF11hAHvTZO"],"_csrf_token":"HsYgrD2YBaWAabOYceN0hluNRnGuz49XiplmMPt43aY="}
|
||||
|
||||
func parseCookie52(cookie, secretKeyBase, authSalt string) ([]byte, error) {
|
||||
ecookie, err := url.QueryUnescape(cookie)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vectors := strings.Split(ecookie, "--")
|
||||
|
||||
body, err := base64.RawStdEncoding.DecodeString(vectors[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iv, err := base64.RawStdEncoding.DecodeString(vectors[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag, err := base64.StdEncoding.DecodeString(vectors[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := pbkdf2.Key([]byte(secretKeyBase), []byte(authSalt),
|
||||
1000, 32, sha1.New)
|
||||
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gcm.Open(nil, iv, append(body, tag...), nil)
|
||||
}
|
Reference in New Issue
Block a user