mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-07-06 21:37:47 +02:00
feat: add Safari password extraction from macOS Keychain (#568)
This commit is contained in:
@@ -3,6 +3,14 @@
|
||||
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"
|
||||
)
|
||||
|
||||
@@ -99,3 +107,90 @@ func platformBrowsers() []types.BrowserConfig {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// keychainPasswordSetter is an optional capability interface satisfied by
|
||||
// Safari, which reads InternetPassword records directly from the login keychain.
|
||||
type keychainPasswordSetter interface {
|
||||
SetKeychainPassword(string)
|
||||
}
|
||||
|
||||
// newPlatformInjector returns a closure that injects the Chromium master-key
|
||||
// retriever and the Safari Keychain password into each Browser.
|
||||
//
|
||||
// Resolution is lazy: the keychain password prompt and retriever construction
|
||||
// are deferred until the first Browser that actually needs them passes through
|
||||
// the closure. Browsers that satisfy neither setter interface (e.g. Firefox)
|
||||
// short-circuit without ever touching the keychain, so `-b firefox` on macOS
|
||||
// no longer triggers a password prompt.
|
||||
func newPlatformInjector(opts PickOptions) func(Browser) {
|
||||
var (
|
||||
password string
|
||||
retriever keyretriever.KeyRetriever
|
||||
resolved bool
|
||||
)
|
||||
return func(b Browser) {
|
||||
rs, needsRetriever := b.(retrieverSetter)
|
||||
kps, needsKeychainPassword := b.(keychainPasswordSetter)
|
||||
if !needsRetriever && !needsKeychainPassword {
|
||||
return
|
||||
}
|
||||
if !resolved {
|
||||
password = resolveKeychainPassword(opts.KeychainPassword)
|
||||
retriever = keyretriever.DefaultRetriever(password)
|
||||
resolved = true
|
||||
}
|
||||
if needsRetriever {
|
||||
rs.SetRetriever(retriever)
|
||||
}
|
||||
if needsKeychainPassword {
|
||||
kps.SetKeychainPassword(password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user