package chromium import ( "os" "path/filepath" "github.com/moond4rk/hackbrowserdata/filemanager" "github.com/moond4rk/hackbrowserdata/log" "github.com/moond4rk/hackbrowserdata/masterkey" "github.com/moond4rk/hackbrowserdata/types" ) // profile is one Chromium profile under an installation — the leaf extraction // unit. It reads its own source files but reuses the installation's master keys. type profile struct { profileDir string browserName string kind types.BrowserKind extractors map[types.Category]categoryExtractor sourcePaths map[types.Category]resolvedPath } func (p *profile) name() string { if p.profileDir == "" { return "" } return filepath.Base(p.profileDir) } func (p *profile) label() string { return p.browserName + "/" + p.name() } // extract copies the profile's source files to a temp directory and extracts the // requested categories, decrypting with the installation's master keys. func (p *profile) extract(masterKeys masterkey.MasterKeys, categories []types.Category) *types.BrowserData { session, err := filemanager.NewSession() if err != nil { log.Debugf("new session for %s: %v", p.label(), err) return &types.BrowserData{} } defer session.Cleanup() tempPaths := p.acquireFiles(session, categories) data := &types.BrowserData{} for _, cat := range categories { path, ok := tempPaths[cat] if !ok { continue } p.extractCategory(data, cat, masterKeys, path) } if accountDB, ok := p.acquireAccountPasswords(session, categories); ok { passwords, err := appendPasswords(masterKeys, accountDB, types.PasswordStoreAccount, data.Passwords) if err != nil { log.Debugf("extract account passwords for %s: %v", p.label(), err) } else { data.Passwords = passwords } } return data } // count counts entries per category without decryption. func (p *profile) count(categories []types.Category) map[types.Category]int { session, err := filemanager.NewSession() if err != nil { log.Debugf("new session for %s: %v", p.label(), err) return nil } defer session.Cleanup() tempPaths := p.acquireFiles(session, categories) counts := make(map[types.Category]int) for _, cat := range categories { path, ok := tempPaths[cat] if !ok { continue } counts[cat] = p.countCategory(cat, path) } if accountDB, ok := p.acquireAccountPasswords(session, categories); ok { if count, err := countPasswords(accountDB); err != nil { log.Debugf("count account passwords for %s: %v", p.label(), err) } else { counts[types.Password] += count } } return counts } // acquireAccountPasswords copies the account-synced password DB into the session, when the // profile has one alongside the local DB. func (p *profile) acquireAccountPasswords(session *filemanager.Session, categories []types.Category) (string, bool) { accountPath, ok := p.accountPasswordPath(categories) if !ok { return "", false } dst := filepath.Join(session.TempDir(), "password_account") if err := session.Acquire(accountPath, dst, false); err != nil { log.Debugf("acquire account passwords for %s: %v", p.label(), err) return "", false } return dst, true } // primaryPasswordStore labels the resolved primary login DB: a profile that only ever synced // passwords has no local Login Data, so the account DB wins candidate resolution. func (p *profile) primaryPasswordStore() string { if primary, ok := p.sourcePaths[types.Password]; ok && primary.rel == accountLoginData { return types.PasswordStoreAccount } return types.PasswordStoreLocal } // accountPasswordPath returns the account-synced password DB when the primary source resolved to // the local Login Data DB. If only Login Data For Account exists, it is already the primary source. func (p *profile) accountPasswordPath(categories []types.Category) (string, bool) { requested := false for _, cat := range categories { if cat == types.Password { requested = true break } } primary, ok := p.sourcePaths[types.Password] if !requested || !ok || primary.rel == accountLoginData || p.kind == types.ChromiumYandex { return "", false } path := filepath.Join(p.profileDir, accountLoginData) info, err := os.Stat(path) return path, err == nil && !info.IsDir() } // acquireFiles copies source files to the session temp directory. func (p *profile) acquireFiles(session *filemanager.Session, categories []types.Category) map[types.Category]string { tempPaths := make(map[types.Category]string) for _, cat := range categories { rp, ok := p.sourcePaths[cat] if !ok { continue } dst := filepath.Join(session.TempDir(), cat.String()) if err := session.Acquire(rp.absPath, dst, rp.isDir); err != nil { log.Debugf("acquire %s: %v", cat, err) continue } tempPaths[cat] = dst } return tempPaths } // extractCategory calls the appropriate extract function for a category. A custom // extractor (registered via extractorsForKind) takes precedence over the switch. func (p *profile) extractCategory(data *types.BrowserData, cat types.Category, masterKeys masterkey.MasterKeys, path string) { if ext, ok := p.extractors[cat]; ok { if err := ext.extract(masterKeys, path, data); err != nil { log.Debugf("extract %s for %s: %v", cat, p.label(), err) } return } var err error switch cat { case types.Password: data.Passwords, err = extractPasswords(masterKeys, path, p.primaryPasswordStore()) case types.Cookie: data.Cookies, err = extractCookies(masterKeys, path) case types.History: data.Histories, err = extractHistories(path) case types.Download: data.Downloads, err = extractDownloads(path) case types.Bookmark: data.Bookmarks, err = extractBookmarks(path) case types.CreditCard: data.CreditCards, err = extractCreditCards(masterKeys, path) case types.Extension: data.Extensions, err = extractExtensions(path) case types.LocalStorage: data.LocalStorage, err = extractLocalStorage(path) case types.SessionStorage: data.SessionStorage, err = extractSessionStorage(path) } if err != nil { log.Debugf("extract %s for %s: %v", cat, p.label(), err) } } // countCategory calls the appropriate count function for a category. func (p *profile) countCategory(cat types.Category, path string) int { var count int var err error switch cat { case types.Password: count, err = countPasswords(path) case types.Cookie: count, err = countCookies(path) case types.History: count, err = countHistories(path) case types.Download: count, err = countDownloads(path) case types.Bookmark: count, err = countBookmarks(path) case types.CreditCard: if p.kind == types.ChromiumYandex { count, err = countYandexCreditCards(path) } else { count, err = countCreditCards(path) } case types.Extension: if p.kind == types.ChromiumOpera { count, err = countOperaExtensions(path) } else { count, err = countExtensions(path) } case types.LocalStorage: count, err = countLocalStorage(path) case types.SessionStorage: count, err = countSessionStorage(path) } if err != nil { log.Debugf("count %s for %s: %v", cat, p.label(), err) } return count }