Files
Roger 00be145044 refactor(browser): publicize key-injection capability api (#597)
Promote keyRetrieversSetter to public KeyManager and lift
keychainPasswordSetter from browser_darwin.go into browser.go as
KeychainPasswordReceiver. Engines already implement these methods;
only type-assertion sites switch to the public interface names.
2026-05-16 12:26:57 +08:00

185 lines
5.6 KiB
Go

//go:build darwin
package browser
import (
"fmt"
"os"
"github.com/moond4rk/keychainbreaker"
"golang.org/x/term"
"github.com/moond4rk/hackbrowserdata/crypto/keyretriever"
"github.com/moond4rk/hackbrowserdata/log"
"github.com/moond4rk/hackbrowserdata/types"
)
func platformBrowsers() []types.BrowserConfig {
return []types.BrowserConfig{
{
Key: "chrome",
Name: chromeName,
Kind: types.Chromium,
KeychainLabel: "Chrome",
UserDataDir: homeDir + "/Library/Application Support/Google/Chrome",
},
{
Key: "edge",
Name: edgeName,
Kind: types.Chromium,
KeychainLabel: "Microsoft Edge",
UserDataDir: homeDir + "/Library/Application Support/Microsoft Edge",
},
{
Key: "chromium",
Name: chromiumName,
Kind: types.Chromium,
KeychainLabel: "Chromium",
UserDataDir: homeDir + "/Library/Application Support/Chromium",
},
{
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,
KeychainLabel: "Opera",
UserDataDir: homeDir + "/Library/Application Support/com.operasoftware.Opera",
},
{
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,
KeychainLabel: "Vivaldi",
UserDataDir: homeDir + "/Library/Application Support/Vivaldi",
},
{
Key: "coccoc",
Name: coccocName,
Kind: types.Chromium,
KeychainLabel: "CocCoc",
UserDataDir: homeDir + "/Library/Application Support/Coccoc",
},
{
Key: "brave",
Name: braveName,
Kind: types.Chromium,
KeychainLabel: "Brave",
UserDataDir: homeDir + "/Library/Application Support/BraveSoftware/Brave-Browser",
},
{
Key: "yandex",
Name: yandexName,
Kind: types.ChromiumYandex,
KeychainLabel: "Yandex",
UserDataDir: homeDir + "/Library/Application Support/Yandex/YandexBrowser",
},
{
Key: "arc",
Name: arcName,
Kind: types.Chromium,
KeychainLabel: "Arc",
UserDataDir: homeDir + "/Library/Application Support/Arc/User Data",
},
{
Key: "firefox",
Name: firefoxName,
Kind: types.Firefox,
UserDataDir: homeDir + "/Library/Application Support/Firefox/Profiles",
},
{
Key: "safari",
Name: safariName,
Kind: types.Safari,
UserDataDir: homeDir + "/Library/Safari",
},
}
}
// resolveKeychainPassword returns the keychain password for macOS.
// If not provided via CLI flag, it prompts interactively when stdin is a TTY.
// After obtaining the password, it verifies against keychainbreaker; on any
// failure it returns "" so downstream code enters "no password" mode rather
// than propagating a known-bad credential. Safari then exports
// keychain-protected entries as metadata-only via keychainbreaker's partial
// extraction mode; Chromium falls back to SecurityCmdRetriever.
func resolveKeychainPassword(flagPassword string) string {
password := flagPassword
if password == "" {
if !term.IsTerminal(int(os.Stdin.Fd())) {
log.Warnf("macOS login password not provided and stdin is not a TTY; keychain-protected data will be exported as metadata only")
return ""
}
fmt.Fprint(os.Stderr, "Enter macOS login password: ")
pwd, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Fprintln(os.Stderr)
if err != nil {
log.Warnf("failed to read macOS login password: %v; keychain-protected data will be exported as metadata only", err)
return ""
}
password = string(pwd)
}
if password == "" {
log.Warnf("no macOS login password entered; keychain-protected data will be exported as metadata only")
return ""
}
// Verify early: try to unlock keychain with keychainbreaker. On failure
// return "" so KeychainPasswordRetriever and Safari both skip the credential
// and rely on their respective fallback paths (SecurityCmdRetriever for
// Chromium, metadata-only export for Safari).
kc, err := keychainbreaker.Open()
if err != nil {
log.Warnf("keychain open failed: %v; keychain-protected data will be exported as metadata only", err)
return ""
}
if err := kc.TryUnlock(keychainbreaker.WithPassword(password)); err != nil {
log.Warnf("keychain unlock failed with provided password; keychain-protected data will be exported as metadata only")
log.Debugf("keychain unlock detail: %v", err)
return ""
}
return password
}
// newPlatformInjector lazily wires retrievers (and the macOS keychain password) into each Browser;
// `-b firefox` never triggers a keychain prompt because lazy resolution skips browsers that need neither.
func newPlatformInjector(opts PickOptions) func(Browser) {
var (
password string
retrievers keyretriever.Retrievers
resolved bool
)
return func(b Browser) {
km, needsRetrievers := b.(KeyManager)
kps, needsKeychainPassword := b.(KeychainPasswordReceiver)
if !needsRetrievers && !needsKeychainPassword {
return
}
if !resolved {
password = resolveKeychainPassword(opts.KeychainPassword)
retrievers = keyretriever.DefaultRetrievers(password)
resolved = true
}
if needsRetrievers {
km.SetKeyRetrievers(retrievers)
}
if needsKeychainPassword {
kps.SetKeychainPassword(password)
}
}
}