Skip to content

Commit 77d743d

Browse files
committed
✨ get cookies from chrome
1 parent b444080 commit 77d743d

5 files changed

+271
-0
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea/

browserCookie.go

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package browsercookie
2+
3+
import (
4+
"net/http"
5+
"net/http/cookiejar"
6+
"os/user"
7+
"time"
8+
"net/url"
9+
"github.com/go-sqlite/sqlite3"
10+
"fmt"
11+
)
12+
13+
/**
14+
Get cookie from chrome
15+
*/
16+
func LoadCookieJarFromChrome(cookileUrl string) (http.CookieJar, error) {
17+
jar, _ := cookiejar.New(nil)
18+
19+
usr, _ := user.Current()
20+
cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir)
21+
cookies, err := ReadChromeCookies(cookiesFile, "", "", time.Time{})
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
cookieURL, _ := url.Parse(cookileUrl)
27+
jar.SetCookies(cookieURL, cookies)
28+
29+
return jar, nil
30+
}
31+
32+
func ReadChromeCookies(filename string, domainFilter string, nameFilter string, expireAfter time.Time) ([]*http.Cookie, error) {
33+
var cookies []*http.Cookie
34+
var unexpectedCols bool
35+
db, err := sqlite3.Open(filename)
36+
if err != nil {
37+
return nil, err
38+
}
39+
defer db.Close()
40+
41+
if err := db.VisitTableRecords("cookies", func(rowId *int64, rec sqlite3.Record) error {
42+
if rowId == nil {
43+
return fmt.Errorf("unexpected nil RowID in Chrome sqlite database")
44+
}
45+
cookie := &http.Cookie{}
46+
47+
if len(rec.Values) != 14 {
48+
unexpectedCols = true
49+
return nil
50+
}
51+
52+
domain, ok := rec.Values[1].(string)
53+
if !ok {
54+
return fmt.Errorf("expected column 2 (host_key) to to be string; got %T", rec.Values[1])
55+
}
56+
name, ok := rec.Values[2].(string)
57+
if !ok {
58+
return fmt.Errorf("expected column 3 (name) in cookie(domain:%s) to to be string; got %T", domain, rec.Values[2])
59+
}
60+
value, ok := rec.Values[3].(string)
61+
if !ok {
62+
return fmt.Errorf("expected column 4 (value) in cookie(domain:%s, name:%s) to to be string; got %T", domain, name, rec.Values[3])
63+
}
64+
path, ok := rec.Values[4].(string)
65+
if !ok {
66+
return fmt.Errorf("expected column 5 (path) in cookie(domain:%s, name:%s) to to be string; got %T", domain, name, rec.Values[4])
67+
}
68+
var expires_utc int64
69+
switch i := rec.Values[5].(type) {
70+
case int64:
71+
expires_utc = i
72+
case int:
73+
if i != 0 {
74+
return fmt.Errorf("expected column 6 (expires_utc) in cookie(domain:%s, name:%s) to to be int64 or int with value=0; got %T with value %v", domain, name, rec.Values[5], rec.Values[5])
75+
}
76+
default:
77+
return fmt.Errorf("expected column 6 (expires_utc) in cookie(domain:%s, name:%s) to to be int64 or int with value=0; got %T with value %v", domain, name, rec.Values[5], rec.Values[5])
78+
}
79+
encrypted_value, ok := rec.Values[12].([]byte)
80+
if !ok {
81+
return fmt.Errorf("expected column 13 (encrypted_value) in cookie(domain:%s, name:%s) to to be []byte; got %T", domain, name, rec.Values[12])
82+
}
83+
84+
var expiry time.Time
85+
if expires_utc != 0 {
86+
expiry = chromeCookieDate(expires_utc)
87+
}
88+
//creation := chromeCookieDate(*rowId)
89+
90+
if domainFilter != "" && domain != domainFilter {
91+
return nil
92+
}
93+
94+
if nameFilter != "" && name != nameFilter {
95+
return nil
96+
}
97+
98+
if !expiry.IsZero() && expiry.Before(expireAfter) {
99+
return nil
100+
}
101+
102+
cookie.Domain = domain
103+
cookie.Name = name
104+
cookie.Path = path
105+
cookie.Expires = expiry
106+
//cookie.Creation = creation
107+
cookie.Secure = rec.Values[6] == 1
108+
cookie.HttpOnly = rec.Values[7] == 1
109+
110+
if len(encrypted_value) > 0 {
111+
decrypted, err := decryptValue(encrypted_value)
112+
if err != nil {
113+
return fmt.Errorf("decrypting cookie %v: %v", cookie, err)
114+
}
115+
cookie.Value = decrypted
116+
} else {
117+
cookie.Value = value
118+
}
119+
cookies = append(cookies, cookie)
120+
121+
return nil
122+
}); err != nil {
123+
return nil, err
124+
}
125+
126+
if cookies == nil && unexpectedCols {
127+
return nil, fmt.Errorf("no cookies found, but we skipped rows with an unexpected number of columns (not 14)")
128+
}
129+
return cookies, nil
130+
}
131+
132+
// See https://cs.chromium.org/chromium/src/base/time/time.h?l=452&rcl=fceb9a030c182e939a436a540e6dacc70f161cb1
133+
const windowsToUnixMicrosecondsOffset = 11644473600000000
134+
135+
// chromeCookieDate converts microseconds to a time.Time object,
136+
// accounting for the switch to Windows epoch (Jan 1 1601).
137+
func chromeCookieDate(timestamp_utc int64) time.Time {
138+
if timestamp_utc > windowsToUnixMicrosecondsOffset {
139+
timestamp_utc -= windowsToUnixMicrosecondsOffset
140+
}
141+
142+
return time.Unix(timestamp_utc/1000000, (timestamp_utc%1000000)*1000)
143+
}
144+

browserCookie_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package browsercookie
2+
3+
import (
4+
"testing"
5+
"fmt"
6+
"time"
7+
"os/user"
8+
)
9+
10+
func TestReadChromeCookies(t *testing.T) {
11+
cookieJar, _ := LoadCookieJarFromChrome("")
12+
fmt.Println("CookieJar=", cookieJar)
13+
14+
usr, _ := user.Current()
15+
cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir)
16+
cookies, _ := ReadChromeCookies(cookiesFile, "", "", time.Time{})
17+
fmt.Println("Cookies=", cookies)
18+
}

chrome_darwin.go

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package browsercookie
2+
3+
import (
4+
"crypto/aes"
5+
"crypto/cipher"
6+
"crypto/sha1"
7+
"errors"
8+
"fmt"
9+
10+
"golang.org/x/crypto/pbkdf2"
11+
12+
keychain "github.com/keybase/go-keychain"
13+
)
14+
15+
// Thanks to https://gist.github.com/dacort/bd6a5116224c594b14db.
16+
17+
const (
18+
salt = "saltysalt"
19+
iv = " "
20+
length = 16
21+
iterations = 1003
22+
)
23+
24+
// keychainPassword is a cache of the password read from the keychain.
25+
var keychainPassword []byte
26+
27+
// setChromeKeychainPassword exists so tests can avoid trying to read
28+
// the Keychain.
29+
func setChromeKeychainPassword(password []byte) []byte {
30+
oldPassword := keychainPassword
31+
keychainPassword = password
32+
return oldPassword
33+
}
34+
35+
// getKeychainPassword retrieves the Chrome Safe Storage password,
36+
// caching it for future calls.
37+
func getKeychainPassword() ([]byte, error) {
38+
if keychainPassword == nil {
39+
password, err := keychain.GetGenericPassword("Chrome Safe Storage", "Chrome", "", "")
40+
if err != nil {
41+
return nil, fmt.Errorf("error reading 'Chrome Safe Storage' keychain password: %v", err)
42+
}
43+
keychainPassword = password
44+
}
45+
return keychainPassword, nil
46+
}
47+
48+
func decryptValue(encrypted []byte) (string, error) {
49+
if len(encrypted) == 0 {
50+
return "", errors.New("empty encrypted value")
51+
}
52+
53+
if len(encrypted) <= 3 {
54+
return "", fmt.Errorf("too short encrypted value (%d<=3)", len(encrypted))
55+
}
56+
57+
encrypted = encrypted[3:]
58+
59+
password, err := getKeychainPassword()
60+
if err != nil {
61+
return "", err
62+
}
63+
64+
key := pbkdf2.Key(password, []byte(salt), iterations, length, sha1.New)
65+
block, err := aes.NewCipher(key)
66+
if err != nil {
67+
return "", err
68+
}
69+
70+
decrypted := make([]byte, len(encrypted))
71+
cbc := cipher.NewCBCDecrypter(block, []byte(iv))
72+
cbc.CryptBlocks(decrypted, encrypted)
73+
74+
plainText, err := aesStripPadding(decrypted)
75+
if err != nil {
76+
return "", err
77+
}
78+
return string(plainText), nil
79+
}
80+
81+
// In the padding scheme the last <padding length> bytes
82+
// have a value equal to the padding length, always in (1,16]
83+
func aesStripPadding(data []byte) ([]byte, error) {
84+
if len(data)%length != 0 {
85+
return nil, fmt.Errorf("decrypted data block length is not a multiple of %d", length)
86+
}
87+
paddingLen := int(data[len(data)-1])
88+
if paddingLen > 16 {
89+
return nil, fmt.Errorf("invalid last block padding length: %d", paddingLen)
90+
}
91+
return data[:len(data)-paddingLen], nil
92+
}
93+

cmd/main.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import (
4+
".."
5+
"fmt"
6+
"log"
7+
)
8+
9+
func main() {
10+
cookieJar, err := browsercookie.LoadCookieJarFromChrome("")
11+
if err != nil {
12+
log.Fatal(err)
13+
}
14+
fmt.Println(cookieJar)
15+
}

0 commit comments

Comments
 (0)