From 40c87b989e1aba72915161d70840509ab2bb528d Mon Sep 17 00:00:00 2001 From: Simon Lehn <48837958+srlehn@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:54:07 +0100 Subject: [PATCH 1/2] add KeyringConfig to browser/chromium for chromium-derivative cookie decryption --- browser/chromium/chromium.go | 52 +++++++++++++++++++++++++++++----- internal/chrome/README.md | 6 ++-- internal/chrome/cookiestore.go | 2 +- internal/chrome/derivatives.go | 48 +++++++++++++++---------------- 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/browser/chromium/chromium.go b/browser/chromium/chromium.go index 5a20023..8ee8e9c 100644 --- a/browser/chromium/chromium.go +++ b/browser/chromium/chromium.go @@ -8,23 +8,61 @@ import ( "github.com/browserutils/kooky/internal/cookies" ) -func ReadCookies(ctx context.Context, filename string, filters ...kooky.Filter) ([]*kooky.Cookie, error) { - return cookies.SingleRead(cookieStore, filename, filters...).ReadAllCookies(ctx) +// KeyringConfig configures how the cookie store retrieves +// the decryption key from the system keyring. +// +// All fields are optional. When empty, defaults are derived +// from the browser name (e.g. "Chromium" → "Chromium Safe Storage"). +// +// See the exported Keyring* variables for known browser configurations. +type KeyringConfig struct { + Account string // keychain account, e.g. "Chrome", "Microsoft Edge" + Storage string // keychain entry, e.g. "Chrome Safe Storage" (derived from Account if empty) + Application string // secret service / kwallet app attr, e.g. "chrome" (derived from Account if empty) + PortalAppID string // xdg-desktop-portal app ID, e.g. "org.chromium.Chromium" } -func TraverseCookies(filename string, filters ...kooky.Filter) kooky.CookieSeq { - return cookies.SingleRead(cookieStore, filename, filters...) +// Known keyring configurations for Chromium-based browsers +// that do not have their own package. +var ( + // linux: $XDG_CONFIG_HOME/vivaldi/Default/Cookies + KeyringConfigVivaldiLinux = &KeyringConfig{Account: `Chrome`, PortalAppID: `com.vivaldi.Vivaldi`} + KeyringConfigVivaldiDarwin = &KeyringConfig{Account: `Vivaldi`} + + // Application may be "arc" or "chrome" depending on version + KeyringConfigArc = &KeyringConfig{Account: `Arc`} +) + +func ReadCookies(ctx context.Context, filename string, keyring *KeyringConfig, filters ...kooky.Filter) ([]*kooky.Cookie, error) { + return cookies.SingleRead(cookieStoreFunc(keyring), filename, filters...).ReadAllCookies(ctx) +} + +func TraverseCookies(filename string, keyring *KeyringConfig, filters ...kooky.Filter) kooky.CookieSeq { + return cookies.SingleRead(cookieStoreFunc(keyring), filename, filters...) } // CookieStore has to be closed with CookieStore.Close() after use. -func CookieStore(filename string, filters ...kooky.Filter) (kooky.CookieStore, error) { - return cookieStore(filename, filters...) +func CookieStore(filename string, keyring *KeyringConfig, filters ...kooky.Filter) (kooky.CookieStore, error) { + return cookieStore(filename, keyring, filters...) +} + +func cookieStoreFunc(keyring *KeyringConfig) func(filename string, filters ...kooky.Filter) (*cookies.CookieJar, error) { + return func(filename string, filters ...kooky.Filter) (*cookies.CookieJar, error) { + return cookieStore(filename, keyring, filters...) + } } -func cookieStore(filename string, filters ...kooky.Filter) (*cookies.CookieJar, error) { +func cookieStore(filename string, keyring *KeyringConfig, filters ...kooky.Filter) (*cookies.CookieJar, error) { s := &chrome.CookieStore{} s.FileNameStr = filename s.BrowserStr = `chromium` + if keyring != nil { + s.SetSafeStorage(keyring.Account, keyring.Storage, keyring.Application) + if len(keyring.PortalAppID) > 0 { + s.SetPortalAppID(keyring.PortalAppID) + } + } + return cookies.NewCookieJar(s, filters...), nil } diff --git a/internal/chrome/README.md b/internal/chrome/README.md index 43bbc26..f3034cb 100644 --- a/internal/chrome/README.md +++ b/internal/chrome/README.md @@ -1,4 +1,6 @@ -### Known Safe Storage combinations +# Chromium + +## Known Safe Storage combinations | Account | Name | Application | Portal App ID | |----------------|-----------------------------|-------------------|-----------------------| @@ -10,7 +12,7 @@ | Vivaldi | (portal only) | vivaldi | com.vivaldi.Vivaldi | | Arc | Arc Safe Storage | arc | | -### SQL schemes of file Cookies, table cookies +## SQL schemes of file Cookies, table cookies extracted with sqlitebrowser diff --git a/internal/chrome/cookiestore.go b/internal/chrome/cookiestore.go index 8c06679..93ad66c 100644 --- a/internal/chrome/cookiestore.go +++ b/internal/chrome/cookiestore.go @@ -16,7 +16,7 @@ type CookieStore struct { KeyringPasswordBytes []byte PasswordBytes []byte DecryptionMethod func(data, password []byte, dbVersion int64) ([]byte, error) - storage safeStorage + keyringConfig safeStorage dbVersion int64 dbFile *os.File } diff --git a/internal/chrome/derivatives.go b/internal/chrome/derivatives.go index 4df601a..e459c3c 100644 --- a/internal/chrome/derivatives.go +++ b/internal/chrome/derivatives.go @@ -9,69 +9,69 @@ import ( type safeStorage struct { account string // e.g. "Chromium", "Chrome", "Microsoft Edge" - name string // e.g. "Chromium Safe Storage" + storage string // e.g. "Chromium Safe Storage" application string // Secret Service application attr, e.g. "chromium", "chrome" portalAppID string // xdg-desktop-portal app ID, e.g. "com.vivaldi.Vivaldi" } -func (s *CookieStore) SetSafeStorage(account, name, application string) { +func (s *CookieStore) SetSafeStorage(account, storage, application string) { if s == nil { return } if len(account) == 0 && len(s.BrowserStr) > 0 { account = cases.Title(language.English, cases.Compact).String(s.BrowserStr) } - if len(name) == 0 { - name = account + ` Safe Storage` + if len(storage) == 0 { + storage = account + ` Safe Storage` } if len(application) == 0 { application = strings.ToLower(account) } - s.storage.account = account - s.storage.name = name - s.storage.application = application + s.keyringConfig.account = account + s.keyringConfig.storage = storage + s.keyringConfig.application = application } -func (s *CookieStore) safeStorageName() string { +func (s *CookieStore) SetPortalAppID(id string) { if s == nil { - return `` + return } - if len(s.storage.name) == 0 { + s.keyringConfig.portalAppID = id +} + +func (s *CookieStore) ensureKeyring() { + if s != nil && len(s.keyringConfig.storage) == 0 { s.SetSafeStorage(``, ``, ``) } - return s.storage.name } func (s *CookieStore) safeStorageAccount() string { if s == nil { return `` } - if len(s.storage.name) == 0 { - s.SetSafeStorage(``, ``, ``) - } - return s.storage.account + s.ensureKeyring() + return s.keyringConfig.account } -func (s *CookieStore) safeStorageApplication() string { +func (s *CookieStore) safeStorageName() string { if s == nil { return `` } - if len(s.storage.name) == 0 { - s.SetSafeStorage(``, ``, ``) - } - return s.storage.application + s.ensureKeyring() + return s.keyringConfig.storage } -func (s *CookieStore) SetPortalAppID(id string) { +func (s *CookieStore) safeStorageApplication() string { if s == nil { - return + return `` } - s.storage.portalAppID = id + s.ensureKeyring() + return s.keyringConfig.application } func (s *CookieStore) portalAppIDValue() string { if s == nil { return `` } - return s.storage.portalAppID + return s.keyringConfig.portalAppID } From 66c5d21f2194b98f0fd49654b0f2b3c7dc4ec041 Mon Sep 17 00:00:00 2001 From: Simon Lehn <48837958+srlehn@users.noreply.github.com> Date: Sun, 22 Mar 2026 03:48:29 +0100 Subject: [PATCH 2/2] chromium: add Browser field to KeyringConfig to set Cookie.Browser identity --- browser/chromium/chromium.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/browser/chromium/chromium.go b/browser/chromium/chromium.go index 8ee8e9c..f2459e0 100644 --- a/browser/chromium/chromium.go +++ b/browser/chromium/chromium.go @@ -16,6 +16,7 @@ import ( // // See the exported Keyring* variables for known browser configurations. type KeyringConfig struct { + Browser string // browser name for display/filtering, e.g. "vivaldi" (defaults to "chromium") Account string // keychain account, e.g. "Chrome", "Microsoft Edge" Storage string // keychain entry, e.g. "Chrome Safe Storage" (derived from Account if empty) Application string // secret service / kwallet app attr, e.g. "chrome" (derived from Account if empty) @@ -26,11 +27,12 @@ type KeyringConfig struct { // that do not have their own package. var ( // linux: $XDG_CONFIG_HOME/vivaldi/Default/Cookies - KeyringConfigVivaldiLinux = &KeyringConfig{Account: `Chrome`, PortalAppID: `com.vivaldi.Vivaldi`} - KeyringConfigVivaldiDarwin = &KeyringConfig{Account: `Vivaldi`} + KeyringConfigVivaldiLinux = &KeyringConfig{Browser: `vivaldi`, Account: `Chrome`, PortalAppID: `com.vivaldi.Vivaldi`} + // macOS: ~/Library/Application Support/Vivaldi/Default/Cookies + KeyringConfigVivaldiDarwin = &KeyringConfig{Browser: `vivaldi`, Account: `Vivaldi`} // Application may be "arc" or "chrome" depending on version - KeyringConfigArc = &KeyringConfig{Account: `Arc`} + KeyringConfigArc = &KeyringConfig{Browser: `arc`, Account: `Arc`} ) func ReadCookies(ctx context.Context, filename string, keyring *KeyringConfig, filters ...kooky.Filter) ([]*kooky.Cookie, error) { @@ -56,6 +58,9 @@ func cookieStore(filename string, keyring *KeyringConfig, filters ...kooky.Filte s := &chrome.CookieStore{} s.FileNameStr = filename s.BrowserStr = `chromium` + if keyring != nil && len(keyring.Browser) > 0 { + s.BrowserStr = keyring.Browser + } if keyring != nil { s.SetSafeStorage(keyring.Account, keyring.Storage, keyring.Application)