Files

117 lines
3.6 KiB
Go

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) != ""
}