Files
HackBrowserData/crypto/keyretriever/masterkeys.go
T
Roger 7b9a973c9c fix: per-tier master-key retrievers for mixed-cipher profiles (#579)
* fix: per-tier master-key retrievers for mixed-cipher profiles
2026-04-20 17:33:38 +08:00

73 lines
2.9 KiB
Go

package keyretriever
import (
"errors"
"fmt"
)
// MasterKeys bundles the per-cipher-version Chromium master keys used to decrypt data from a
// single profile. decryptValue dispatches on the ciphertext's version prefix and picks the
// matching key; a missing (nil) key for a tier means "that cipher version cannot be decrypted",
// but the other tiers remain usable — a Chrome 127+ profile upgraded from pre-127 carries mixed
// v10+v20 ciphertexts, and Linux profiles may carry mixed v10+v11 for analogous reasons.
//
// - V10: Chrome 80+ key with "v10" cipher prefix.
// - Windows: os_crypt.encrypted_key decrypted by user-level DPAPI (AES-GCM ciphertexts).
// - macOS: derived from Keychain via PBKDF2(1003, SHA-1) (AES-CBC ciphertexts).
// - Linux: derived from "peanuts" hardcoded password (Chromium's kV10Key, AES-CBC).
// - V11: Chrome Linux key with "v11" cipher prefix, derived from D-Bus Secret Service
// (KWallet / GNOME Keyring) via PBKDF2. Nil on Windows and macOS (v11 prefix not used there).
// - V20: Chrome 127+ Windows key with "v20" cipher prefix, retrieved via reflective injection
// into the browser's elevation service. Nil on non-Windows platforms.
type MasterKeys struct {
V10 []byte
V11 []byte
V20 []byte
}
// Retrievers is the per-tier retriever configuration passed to NewMasterKeys. Each slot runs
// independently — failure or absence of one tier does not affect others. Platform injectors set
// only the slots that apply to their platform and leave the rest nil (e.g. Linux populates
// V10+V11, leaves V20 nil).
type Retrievers struct {
V10 KeyRetriever
V11 KeyRetriever
V20 KeyRetriever
}
// NewMasterKeys fetches every configured tier in r independently and returns the assembled
// MasterKeys together with any per-tier errors joined into one. Nil retrievers and retrievers
// returning (nil, nil) (the "not applicable" signal — e.g. ABERetriever on a non-ABE fork)
// contribute nil keys silently; only non-nil errors propagate.
//
// The returned error, when non-nil, is an errors.Join of per-tier failures formatted as
// "<tier>: <err>" (e.g. "v10: dpapi decrypt: ..."). Callers are expected to log it at whatever
// severity fits their context — this function itself never logs, leaving logging policy to its
// callers. Other pieces of the keyretriever package (e.g. ChainRetriever) may still log on their
// own failures; the "no-logging" guarantee is scoped to NewMasterKeys.
func NewMasterKeys(r Retrievers, storage, localStatePath string) (MasterKeys, error) {
var keys MasterKeys
var errs []error
for _, t := range []struct {
name string
r KeyRetriever
dst *[]byte
}{
{"v10", r.V10, &keys.V10},
{"v11", r.V11, &keys.V11},
{"v20", r.V20, &keys.V20},
} {
if t.r == nil {
continue
}
k, err := t.r.RetrieveKey(storage, localStatePath)
if err != nil {
errs = append(errs, fmt.Errorf("%s: %w", t.name, err))
continue
}
*t.dst = k
}
return keys, errors.Join(errs...)
}