mirror of
https://github.com/moonD4rk/HackBrowserData.git
synced 2026-05-19 18:58:03 +02:00
refactor(chromium): isolate Yandex protocol specifics design (#598)
This commit is contained in:
@@ -119,14 +119,3 @@ func countCreditCards(path string) (int, error) {
|
||||
func countYandexCreditCards(path string) (int, error) {
|
||||
return sqliteutil.CountRows(path, false, yandexCreditCardCountQuery)
|
||||
}
|
||||
|
||||
// yandexCardAAD is the raw guid bytes (+ keyID if the profile has a master password).
|
||||
func yandexCardAAD(guid string, keyID []byte) []byte {
|
||||
if len(keyID) == 0 {
|
||||
return []byte(guid)
|
||||
}
|
||||
out := make([]byte, 0, len(guid)+len(keyID))
|
||||
out = append(out, guid...)
|
||||
out = append(out, keyID...)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"sort"
|
||||
@@ -99,26 +98,3 @@ func extractYandexPasswords(keys keyretriever.MasterKeys, path string) ([]types.
|
||||
func countPasswords(path string) (int, error) {
|
||||
return sqliteutil.CountRows(path, false, countLoginQuery)
|
||||
}
|
||||
|
||||
// yandexLoginAAD is SHA1(origin_url \x00 username_element \x00 username_value \x00 password_element \x00 signon_realm),
|
||||
// with keyID appended when the profile has a master password (v1 always passes nil).
|
||||
func yandexLoginAAD(originURL, usernameElem, usernameVal, passwordElem, signonRealm string, keyID []byte) []byte {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(originURL))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(usernameElem))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(usernameVal))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(passwordElem))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(signonRealm))
|
||||
sum := h.Sum(nil)
|
||||
if len(keyID) == 0 {
|
||||
return sum
|
||||
}
|
||||
out := make([]byte, 0, len(sum)+len(keyID))
|
||||
out = append(out, sum...)
|
||||
out = append(out, keyID...)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -32,24 +32,6 @@ var chromiumSources = map[types.Category][]sourcePath{
|
||||
types.SessionStorage: {dir("Session Storage")},
|
||||
}
|
||||
|
||||
// yandexSourceOverrides contains only the entries that differ from chromiumSources.
|
||||
var yandexSourceOverrides = map[types.Category][]sourcePath{
|
||||
types.Password: {file("Ya Passman Data")},
|
||||
types.CreditCard: {file("Ya Credit Cards")},
|
||||
}
|
||||
|
||||
// yandexSources returns chromiumSources with Yandex-specific overrides applied.
|
||||
func yandexSources() map[types.Category][]sourcePath {
|
||||
sources := make(map[types.Category][]sourcePath, len(chromiumSources))
|
||||
for k, v := range chromiumSources {
|
||||
sources[k] = v
|
||||
}
|
||||
for k, v := range yandexSourceOverrides {
|
||||
sources[k] = v
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
// sourcesForKind returns the source mapping for a browser kind.
|
||||
func sourcesForKind(kind types.BrowserKind) map[types.Category][]sourcePath {
|
||||
switch kind {
|
||||
@@ -106,13 +88,6 @@ func (e creditCardExtractor) extract(keys keyretriever.MasterKeys, path string,
|
||||
return err
|
||||
}
|
||||
|
||||
// yandexExtractors overrides Password and CreditCard extraction for Yandex, which wraps its data-encryption key inside
|
||||
// meta.local_encryptor_data, binds per-row AAD to GCM, and stores cards as JSON blobs in a records table.
|
||||
var yandexExtractors = map[types.Category]categoryExtractor{
|
||||
types.Password: passwordExtractor{fn: extractYandexPasswords},
|
||||
types.CreditCard: creditCardExtractor{fn: extractYandexCreditCards},
|
||||
}
|
||||
|
||||
// operaExtractors overrides Extension extraction for Opera,
|
||||
// which stores settings under "extensions.opsettings".
|
||||
var operaExtractors = map[types.Category]categoryExtractor{
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/crypto"
|
||||
"github.com/moond4rk/hackbrowserdata/types"
|
||||
)
|
||||
|
||||
// yandexSourceOverrides contains only the entries that differ from chromiumSources.
|
||||
var yandexSourceOverrides = map[types.Category][]sourcePath{
|
||||
types.Password: {file("Ya Passman Data")},
|
||||
types.CreditCard: {file("Ya Credit Cards")},
|
||||
}
|
||||
|
||||
// yandexSources returns chromiumSources with Yandex-specific overrides applied.
|
||||
func yandexSources() map[types.Category][]sourcePath {
|
||||
sources := make(map[types.Category][]sourcePath, len(chromiumSources))
|
||||
for k, v := range chromiumSources {
|
||||
sources[k] = v
|
||||
}
|
||||
for k, v := range yandexSourceOverrides {
|
||||
sources[k] = v
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
// yandexExtractors overrides Password and CreditCard extraction for Yandex, which wraps its data-encryption key inside
|
||||
// meta.local_encryptor_data, binds per-row AAD to GCM, and stores cards as JSON blobs in a records table.
|
||||
var yandexExtractors = map[types.Category]categoryExtractor{
|
||||
types.Password: passwordExtractor{fn: extractYandexPasswords},
|
||||
types.CreditCard: creditCardExtractor{fn: extractYandexCreditCards},
|
||||
}
|
||||
|
||||
// yandexLoginAAD is SHA1(origin_url \x00 username_element \x00 username_value \x00 password_element \x00 signon_realm),
|
||||
// with keyID appended when the profile has a master password (v1 always passes nil).
|
||||
func yandexLoginAAD(originURL, usernameElem, usernameVal, passwordElem, signonRealm string, keyID []byte) []byte {
|
||||
h := sha1.New()
|
||||
h.Write([]byte(originURL))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(usernameElem))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(usernameVal))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(passwordElem))
|
||||
h.Write([]byte{0})
|
||||
h.Write([]byte(signonRealm))
|
||||
sum := h.Sum(nil)
|
||||
if len(keyID) == 0 {
|
||||
return sum
|
||||
}
|
||||
out := make([]byte, 0, len(sum)+len(keyID))
|
||||
out = append(out, sum...)
|
||||
out = append(out, keyID...)
|
||||
return out
|
||||
}
|
||||
|
||||
// yandexCardAAD is the raw guid bytes (+ keyID if the profile has a master password).
|
||||
func yandexCardAAD(guid string, keyID []byte) []byte {
|
||||
if len(keyID) == 0 {
|
||||
return []byte(guid)
|
||||
}
|
||||
out := make([]byte, 0, len(guid)+len(keyID))
|
||||
out = append(out, guid...)
|
||||
out = append(out, keyID...)
|
||||
return out
|
||||
}
|
||||
|
||||
// errYandexMasterPasswordSet: caller warns + skips; RSA-OAEP unseal is deferred (RFC-012 §6).
|
||||
var errYandexMasterPasswordSet = errors.New("yandex: profile protected by master password, skipping")
|
||||
|
||||
// loadYandexDataKey honors the master-password gate and returns the per-DB data key. See RFC-012 §4.2.
|
||||
func loadYandexDataKey(dbPath string, masterKey []byte) ([]byte, error) {
|
||||
if len(masterKey) == 0 {
|
||||
return nil, fmt.Errorf("yandex: master key not available")
|
||||
}
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil, fmt.Errorf("yandex db file: %w", err)
|
||||
}
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if hasMasterPassword(db) {
|
||||
return nil, errYandexMasterPasswordSet
|
||||
}
|
||||
|
||||
var blob []byte
|
||||
if err := db.QueryRow("SELECT value FROM meta WHERE key = 'local_encryptor_data'").Scan(&blob); err != nil {
|
||||
return nil, fmt.Errorf("read local_encryptor_data: %w", err)
|
||||
}
|
||||
|
||||
dataKey, err := crypto.DecryptYandexIntermediateKey(masterKey, blob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("derive yandex data key: %w", err)
|
||||
}
|
||||
return dataKey, nil
|
||||
}
|
||||
|
||||
// hasMasterPassword: missing table (Ya Credit Cards) or empty sealed_key both mean false.
|
||||
func hasMasterPassword(db *sql.DB) bool {
|
||||
var sealed sql.NullString
|
||||
if err := db.QueryRow("SELECT sealed_key FROM active_keys").Scan(&sealed); err != nil {
|
||||
return false
|
||||
}
|
||||
return sealed.Valid && strings.TrimSpace(sealed.String) != ""
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
|
||||
"github.com/moond4rk/hackbrowserdata/crypto"
|
||||
)
|
||||
|
||||
// errYandexMasterPasswordSet: caller warns + skips; RSA-OAEP unseal is deferred (RFC-012 §6).
|
||||
var errYandexMasterPasswordSet = errors.New("yandex: profile protected by master password, skipping")
|
||||
|
||||
// loadYandexDataKey honors the master-password gate and returns the per-DB data key. See RFC-012 §4.2.
|
||||
func loadYandexDataKey(dbPath string, masterKey []byte) ([]byte, error) {
|
||||
if len(masterKey) == 0 {
|
||||
return nil, fmt.Errorf("yandex: master key not available")
|
||||
}
|
||||
if _, err := os.Stat(dbPath); err != nil {
|
||||
return nil, fmt.Errorf("yandex db file: %w", err)
|
||||
}
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if hasMasterPassword(db) {
|
||||
return nil, errYandexMasterPasswordSet
|
||||
}
|
||||
|
||||
var blob []byte
|
||||
if err := db.QueryRow("SELECT value FROM meta WHERE key = 'local_encryptor_data'").Scan(&blob); err != nil {
|
||||
return nil, fmt.Errorf("read local_encryptor_data: %w", err)
|
||||
}
|
||||
|
||||
dataKey, err := crypto.DecryptYandexIntermediateKey(masterKey, blob)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("derive yandex data key: %w", err)
|
||||
}
|
||||
return dataKey, nil
|
||||
}
|
||||
|
||||
// hasMasterPassword: missing table (Ya Credit Cards) or empty sealed_key both mean false.
|
||||
func hasMasterPassword(db *sql.DB) bool {
|
||||
var sealed sql.NullString
|
||||
if err := db.QueryRow("SELECT sealed_key FROM active_keys").Scan(&sealed); err != nil {
|
||||
return false
|
||||
}
|
||||
return sealed.Valid && strings.TrimSpace(sealed.String) != ""
|
||||
}
|
||||
Reference in New Issue
Block a user