mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
refactor(browser): simplify credential storage config (#593)
This commit is contained in:
+55
-55
@@ -17,81 +17,81 @@ import (
|
||||
func platformBrowsers() []types.BrowserConfig {
|
||||
return []types.BrowserConfig{
|
||||
{
|
||||
Key: "chrome",
|
||||
Name: chromeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chrome",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Google/Chrome",
|
||||
Key: "chrome",
|
||||
Name: chromeName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chrome",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Google/Chrome",
|
||||
},
|
||||
{
|
||||
Key: "edge",
|
||||
Name: edgeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Microsoft Edge",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Microsoft Edge",
|
||||
Key: "edge",
|
||||
Name: edgeName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Microsoft Edge",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Microsoft Edge",
|
||||
},
|
||||
{
|
||||
Key: "chromium",
|
||||
Name: chromiumName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chromium",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Chromium",
|
||||
Key: "chromium",
|
||||
Name: chromiumName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chromium",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Chromium",
|
||||
},
|
||||
{
|
||||
Key: "chrome-beta",
|
||||
Name: chromeBetaName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chrome",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Google/Chrome Beta",
|
||||
Key: "chrome-beta",
|
||||
Name: chromeBetaName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chrome",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Google/Chrome Beta",
|
||||
},
|
||||
{
|
||||
Key: "opera",
|
||||
Name: operaName,
|
||||
Kind: types.ChromiumOpera,
|
||||
Storage: "Opera",
|
||||
UserDataDir: homeDir + "/Library/Application Support/com.operasoftware.Opera",
|
||||
Key: "opera",
|
||||
Name: operaName,
|
||||
Kind: types.ChromiumOpera,
|
||||
KeychainLabel: "Opera",
|
||||
UserDataDir: homeDir + "/Library/Application Support/com.operasoftware.Opera",
|
||||
},
|
||||
{
|
||||
Key: "opera-gx",
|
||||
Name: operaGXName,
|
||||
Kind: types.ChromiumOpera,
|
||||
Storage: "Opera",
|
||||
UserDataDir: homeDir + "/Library/Application Support/com.operasoftware.OperaGX",
|
||||
Key: "opera-gx",
|
||||
Name: operaGXName,
|
||||
Kind: types.ChromiumOpera,
|
||||
KeychainLabel: "Opera",
|
||||
UserDataDir: homeDir + "/Library/Application Support/com.operasoftware.OperaGX",
|
||||
},
|
||||
{
|
||||
Key: "vivaldi",
|
||||
Name: vivaldiName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Vivaldi",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Vivaldi",
|
||||
Key: "vivaldi",
|
||||
Name: vivaldiName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Vivaldi",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Vivaldi",
|
||||
},
|
||||
{
|
||||
Key: "coccoc",
|
||||
Name: coccocName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "CocCoc",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Coccoc",
|
||||
Key: "coccoc",
|
||||
Name: coccocName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "CocCoc",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Coccoc",
|
||||
},
|
||||
{
|
||||
Key: "brave",
|
||||
Name: braveName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Brave",
|
||||
UserDataDir: homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
Key: "brave",
|
||||
Name: braveName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Brave",
|
||||
UserDataDir: homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
},
|
||||
{
|
||||
Key: "yandex",
|
||||
Name: yandexName,
|
||||
Kind: types.ChromiumYandex,
|
||||
Storage: "Yandex",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Yandex/YandexBrowser",
|
||||
Key: "yandex",
|
||||
Name: yandexName,
|
||||
Kind: types.ChromiumYandex,
|
||||
KeychainLabel: "Yandex",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Yandex/YandexBrowser",
|
||||
},
|
||||
{
|
||||
Key: "arc",
|
||||
Name: arcName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Arc",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Arc/User Data",
|
||||
Key: "arc",
|
||||
Name: arcName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Arc",
|
||||
UserDataDir: homeDir + "/Library/Application Support/Arc/User Data",
|
||||
},
|
||||
{
|
||||
Key: "firefox",
|
||||
|
||||
+35
-35
@@ -10,53 +10,53 @@ import (
|
||||
func platformBrowsers() []types.BrowserConfig {
|
||||
return []types.BrowserConfig{
|
||||
{
|
||||
Key: "chrome",
|
||||
Name: chromeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/google-chrome",
|
||||
Key: "chrome",
|
||||
Name: chromeName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/google-chrome",
|
||||
},
|
||||
{
|
||||
Key: "edge",
|
||||
Name: edgeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/microsoft-edge",
|
||||
Key: "edge",
|
||||
Name: edgeName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/microsoft-edge",
|
||||
},
|
||||
{
|
||||
Key: "chromium",
|
||||
Name: chromiumName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/chromium",
|
||||
Key: "chromium",
|
||||
Name: chromiumName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/chromium",
|
||||
},
|
||||
{
|
||||
Key: "chrome-beta",
|
||||
Name: chromeBetaName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/google-chrome-beta",
|
||||
Key: "chrome-beta",
|
||||
Name: chromeBetaName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/google-chrome-beta",
|
||||
},
|
||||
{
|
||||
Key: "opera",
|
||||
Name: operaName,
|
||||
Kind: types.ChromiumOpera,
|
||||
Storage: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/opera",
|
||||
Key: "opera",
|
||||
Name: operaName,
|
||||
Kind: types.ChromiumOpera,
|
||||
KeychainLabel: "Chromium Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/opera",
|
||||
},
|
||||
{
|
||||
Key: "vivaldi",
|
||||
Name: vivaldiName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/vivaldi",
|
||||
Key: "vivaldi",
|
||||
Name: vivaldiName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Chrome Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/vivaldi",
|
||||
},
|
||||
{
|
||||
Key: "brave",
|
||||
Name: braveName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "Brave Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/BraveSoftware/Brave-Browser",
|
||||
Key: "brave",
|
||||
Name: braveName,
|
||||
Kind: types.Chromium,
|
||||
KeychainLabel: "Brave Safe Storage",
|
||||
UserDataDir: homeDir + "/.config/BraveSoftware/Brave-Browser",
|
||||
},
|
||||
{
|
||||
Key: "firefox",
|
||||
|
||||
@@ -13,14 +13,14 @@ func platformBrowsers() []types.BrowserConfig {
|
||||
Key: "chrome",
|
||||
Name: chromeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "chrome",
|
||||
WindowsABE: true,
|
||||
UserDataDir: homeDir + "/AppData/Local/Google/Chrome/User Data",
|
||||
},
|
||||
{
|
||||
Key: "edge",
|
||||
Name: edgeName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "edge",
|
||||
WindowsABE: true,
|
||||
UserDataDir: homeDir + "/AppData/Local/Microsoft/Edge/User Data",
|
||||
},
|
||||
{
|
||||
@@ -33,7 +33,7 @@ func platformBrowsers() []types.BrowserConfig {
|
||||
Key: "chrome-beta",
|
||||
Name: chromeBetaName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "chrome-beta",
|
||||
WindowsABE: true,
|
||||
UserDataDir: homeDir + "/AppData/Local/Google/Chrome Beta/User Data",
|
||||
},
|
||||
{
|
||||
@@ -58,14 +58,14 @@ func platformBrowsers() []types.BrowserConfig {
|
||||
Key: "coccoc",
|
||||
Name: coccocName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "coccoc",
|
||||
WindowsABE: true,
|
||||
UserDataDir: homeDir + "/AppData/Local/CocCoc/Browser/User Data",
|
||||
},
|
||||
{
|
||||
Key: "brave",
|
||||
Name: braveName,
|
||||
Kind: types.Chromium,
|
||||
Storage: "brave",
|
||||
WindowsABE: true,
|
||||
UserDataDir: homeDir + "/AppData/Local/BraveSoftware/Brave-Browser/User Data",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,25 +8,38 @@ import (
|
||||
"github.com/moond4rk/hackbrowserdata/utils/winutil"
|
||||
)
|
||||
|
||||
// TestWinUtilTableCoversABEBrowsers verifies that every Windows browser
|
||||
// with ABE support in winutil.Table has a matching Storage key in
|
||||
// platformBrowsers(). A mismatch means adding a new Chromium fork was
|
||||
// incomplete: either the BrowserConfig row lacks Storage: "<key>", or
|
||||
// winutil.Table has a stale entry nobody retrieves keys for.
|
||||
// TestWinUtilTableCoversABEBrowsers verifies that the set of Windows browsers
|
||||
// with WindowsABE: true in platformBrowsers() exactly matches the set of
|
||||
// winutil.Table entries that declare ABE support (keyed by BrowserConfig.Key ==
|
||||
// winutil.Entry.Key). A mismatch means adding a new Chromium fork was
|
||||
// incomplete: either a BrowserConfig row is missing WindowsABE: true, or
|
||||
// winutil.Table has a stale/missing entry.
|
||||
func TestWinUtilTableCoversABEBrowsers(t *testing.T) {
|
||||
storages := make(map[string]struct{})
|
||||
abeConfigs := make(map[string]struct{})
|
||||
for _, b := range platformBrowsers() {
|
||||
if b.Storage != "" {
|
||||
storages[b.Storage] = struct{}{}
|
||||
if b.WindowsABE {
|
||||
abeConfigs[b.Key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
abeTable := make(map[string]struct{})
|
||||
for key, entry := range winutil.Table {
|
||||
if entry.ABE == winutil.ABENone {
|
||||
continue
|
||||
if entry.Key != key {
|
||||
t.Errorf("winutil.Table[%q].Key = %q; map key and Entry.Key must match (winutil.Entry doc invariant)", key, entry.Key)
|
||||
}
|
||||
if _, ok := storages[key]; !ok {
|
||||
t.Errorf("winutil.Table[%q] declares ABE support but no BrowserConfig.Storage matches — either fix the table or set Storage: %q in platformBrowsers()", key, key)
|
||||
if entry.ABE != winutil.ABENone {
|
||||
abeTable[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for key := range abeTable {
|
||||
if _, ok := abeConfigs[key]; !ok {
|
||||
t.Errorf("winutil.Table[%q] declares ABE support but no BrowserConfig with Key %q sets WindowsABE: true — either fix the table or set WindowsABE: true in platformBrowsers()", key, key)
|
||||
}
|
||||
}
|
||||
for key := range abeConfigs {
|
||||
if _, ok := abeTable[key]; !ok {
|
||||
t.Errorf("BrowserConfig with Key %q sets WindowsABE: true but winutil.Table[%q] is missing or declares no ABE — add the table entry", key, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,16 @@ func (b *Browser) getMasterKeys(session *filemanager.Session) keyretriever.Maste
|
||||
break
|
||||
}
|
||||
|
||||
keys, err := keyretriever.NewMasterKeys(b.retrievers, b.cfg.Storage, localStateDst)
|
||||
abeKey := ""
|
||||
if b.cfg.WindowsABE {
|
||||
abeKey = b.cfg.Key
|
||||
}
|
||||
hints := keyretriever.Hints{
|
||||
KeychainLabel: b.cfg.KeychainLabel,
|
||||
WindowsABEKey: abeKey,
|
||||
LocalStatePath: localStateDst,
|
||||
}
|
||||
keys, err := keyretriever.NewMasterKeys(b.retrievers, hints)
|
||||
if err != nil {
|
||||
installKey := b.BrowserName() + "|" + b.cfg.UserDataDir
|
||||
if _, already := warnedMasterKeyFailure.LoadOrStore(installKey, struct{}{}); !already {
|
||||
|
||||
@@ -448,17 +448,15 @@ func TestLocalStatePath(t *testing.T) {
|
||||
|
||||
// mockRetriever records the arguments passed to RetrieveKey.
|
||||
type mockRetriever struct {
|
||||
storage string
|
||||
localState string
|
||||
key []byte
|
||||
err error
|
||||
called bool
|
||||
hints keyretriever.Hints
|
||||
key []byte
|
||||
err error
|
||||
called bool
|
||||
}
|
||||
|
||||
func (m *mockRetriever) RetrieveKey(storage, localStatePath string) ([]byte, error) {
|
||||
func (m *mockRetriever) RetrieveKey(hints keyretriever.Hints) ([]byte, error) {
|
||||
m.called = true
|
||||
m.storage = storage
|
||||
m.localState = localStatePath
|
||||
m.hints = hints
|
||||
return m.key, m.err
|
||||
}
|
||||
|
||||
@@ -472,41 +470,41 @@ func TestGetMasterKeys(t *testing.T) {
|
||||
mkFile(dirNoLocalState, "Default", "History")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dir string
|
||||
storage string
|
||||
retriever keyretriever.KeyRetriever // nil → don't call SetKeyRetrievers
|
||||
wantV10 []byte
|
||||
wantStorage string
|
||||
wantLocalState bool // whether localStatePath passed to retriever is non-empty
|
||||
name string
|
||||
dir string
|
||||
keychainLabel string
|
||||
retriever keyretriever.KeyRetriever // nil → don't call SetKeyRetrievers
|
||||
wantV10 []byte
|
||||
wantKeychainLabel string
|
||||
wantLocalState bool // whether localStatePath passed to retriever is non-empty
|
||||
}{
|
||||
{
|
||||
name: "nil retriever yields empty keys",
|
||||
dir: fixture.chrome,
|
||||
},
|
||||
{
|
||||
name: "with Local State passes path to retriever",
|
||||
dir: fixture.chrome,
|
||||
storage: "Chrome",
|
||||
retriever: &mockRetriever{key: []byte("fake-master-key")},
|
||||
wantV10: []byte("fake-master-key"),
|
||||
wantStorage: "Chrome",
|
||||
wantLocalState: true,
|
||||
name: "with Local State passes path to retriever",
|
||||
dir: fixture.chrome,
|
||||
keychainLabel: "Chrome",
|
||||
retriever: &mockRetriever{key: []byte("fake-master-key")},
|
||||
wantV10: []byte("fake-master-key"),
|
||||
wantKeychainLabel: "Chrome",
|
||||
wantLocalState: true,
|
||||
},
|
||||
{
|
||||
name: "without Local State passes empty path",
|
||||
dir: dirNoLocalState,
|
||||
storage: "Chromium",
|
||||
retriever: &mockRetriever{key: []byte("derived-key")},
|
||||
wantV10: []byte("derived-key"),
|
||||
wantStorage: "Chromium",
|
||||
name: "without Local State passes empty path",
|
||||
dir: dirNoLocalState,
|
||||
keychainLabel: "Chromium",
|
||||
retriever: &mockRetriever{key: []byte("derived-key")},
|
||||
wantV10: []byte("derived-key"),
|
||||
wantKeychainLabel: "Chromium",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
browsers, err := NewBrowsers(types.BrowserConfig{
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: tt.dir, Storage: tt.storage,
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: tt.dir, KeychainLabel: tt.keychainLabel,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, browsers)
|
||||
@@ -531,11 +529,11 @@ func TestGetMasterKeys(t *testing.T) {
|
||||
mock, ok := tt.retriever.(*mockRetriever)
|
||||
require.True(t, ok)
|
||||
assert.True(t, mock.called)
|
||||
assert.Equal(t, tt.wantStorage, mock.storage)
|
||||
assert.Equal(t, tt.wantKeychainLabel, mock.hints.KeychainLabel)
|
||||
if tt.wantLocalState {
|
||||
assert.NotEmpty(t, mock.localState)
|
||||
assert.NotEmpty(t, mock.hints.LocalStatePath)
|
||||
} else {
|
||||
assert.Empty(t, mock.localState)
|
||||
assert.Empty(t, mock.hints.LocalStatePath)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -553,7 +551,7 @@ func TestGetMasterKeys_AllTiersInvoked(t *testing.T) {
|
||||
v20mock := &mockRetriever{key: []byte("fake-v20-key")}
|
||||
|
||||
browsers, err := NewBrowsers(types.BrowserConfig{
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: fixture.chrome, Storage: "Chrome",
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: fixture.chrome, KeychainLabel: "Chrome",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, browsers)
|
||||
@@ -573,8 +571,44 @@ func TestGetMasterKeys_AllTiersInvoked(t *testing.T) {
|
||||
assert.True(t, v11mock.called, "V11 retriever must be called — no silent bypass")
|
||||
assert.True(t, v20mock.called, "V20 retriever must be called — no silent bypass")
|
||||
for _, m := range []*mockRetriever{v10mock, v11mock, v20mock} {
|
||||
assert.Equal(t, "Chrome", m.storage)
|
||||
assert.NotEmpty(t, m.localState, "Local State path must be passed to every retriever")
|
||||
assert.Equal(t, "Chrome", m.hints.KeychainLabel)
|
||||
assert.NotEmpty(t, m.hints.LocalStatePath, "Local State path must be passed to every retriever")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetMasterKeys_WindowsABEThreading pins cfg.WindowsABE → hints.WindowsABEKey threading. A
|
||||
// regression here silently disables Windows ABE decryption with no dev-box-test signal — only the
|
||||
// windows-tunnel sandbox 574-cookie regression would catch it — so it must be pinned at unit level.
|
||||
func TestGetMasterKeys_WindowsABEThreading(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
windowsABE bool
|
||||
wantABEKey string
|
||||
}{
|
||||
{"WindowsABE=true threads cfg.Key into hints.WindowsABEKey", "chrome", true, "chrome"},
|
||||
{"WindowsABE=false leaves hints.WindowsABEKey empty", "opera", false, ""},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mock := &mockRetriever{key: []byte("k")}
|
||||
browsers, err := NewBrowsers(types.BrowserConfig{
|
||||
Key: tt.key, Name: "Test", Kind: types.Chromium,
|
||||
UserDataDir: fixture.chrome, WindowsABE: tt.windowsABE,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, browsers)
|
||||
|
||||
b := browsers[0]
|
||||
b.SetKeyRetrievers(keyretriever.Retrievers{V20: mock})
|
||||
|
||||
session, err := filemanager.NewSession()
|
||||
require.NoError(t, err)
|
||||
defer session.Cleanup()
|
||||
|
||||
b.getMasterKeys(session)
|
||||
assert.Equal(t, tt.wantABEKey, mock.hints.WindowsABEKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,7 +639,7 @@ func TestExtract(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
browsers, err := NewBrowsers(types.BrowserConfig{
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: dir, Storage: "Chrome",
|
||||
Name: "Test", Kind: types.Chromium, UserDataDir: dir, KeychainLabel: "Chrome",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, browsers, 1)
|
||||
|
||||
Reference in New Issue
Block a user