chrome-tools 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +8602 -0
- package/dist/index.d.ts +629 -0
- package/dist/index.js +2297 -0
- package/package.json +62 -29
- package/dist/bin/chrome_abe_decrypt.exe +0 -0
- package/dist/bin/cli.d.ts +0 -9
- package/dist/bin/cli.js +0 -590
- package/dist/src/core/browsers.d.ts +0 -73
- package/dist/src/core/browsers.js +0 -186
- package/dist/src/core/chrome.d.ts +0 -124
- package/dist/src/core/chrome.js +0 -259
- package/dist/src/core/crypto.d.ts +0 -52
- package/dist/src/core/crypto.js +0 -152
- package/dist/src/core/db.d.ts +0 -22
- package/dist/src/core/db.js +0 -227
- package/dist/src/core/keychain.d.ts +0 -57
- package/dist/src/core/keychain.js +0 -191
- package/dist/src/core/limit.d.ts +0 -4
- package/dist/src/core/limit.js +0 -12
- package/dist/src/core/platform/darwin.d.ts +0 -12
- package/dist/src/core/platform/darwin.js +0 -31
- package/dist/src/core/platform/index.d.ts +0 -30
- package/dist/src/core/platform/index.js +0 -69
- package/dist/src/core/platform/linux.d.ts +0 -10
- package/dist/src/core/platform/linux.js +0 -64
- package/dist/src/core/platform/types.d.ts +0 -45
- package/dist/src/core/platform/types.js +0 -1
- package/dist/src/core/platform/win32.d.ts +0 -13
- package/dist/src/core/platform/win32.js +0 -249
- package/dist/src/core/sql.d.ts +0 -2
- package/dist/src/core/sql.js +0 -4
- package/dist/src/index.d.ts +0 -28
- package/dist/src/index.js +0 -34
- package/dist/src/modules/autofill.d.ts +0 -68
- package/dist/src/modules/autofill.js +0 -170
- package/dist/src/modules/bookmarks.d.ts +0 -22
- package/dist/src/modules/bookmarks.js +0 -83
- package/dist/src/modules/cookies.d.ts +0 -40
- package/dist/src/modules/cookies.js +0 -163
- package/dist/src/modules/downloads.d.ts +0 -27
- package/dist/src/modules/downloads.js +0 -80
- package/dist/src/modules/extensions.d.ts +0 -24
- package/dist/src/modules/extensions.js +0 -71
- package/dist/src/modules/history.d.ts +0 -34
- package/dist/src/modules/history.js +0 -83
- package/dist/src/modules/local-storage.d.ts +0 -40
- package/dist/src/modules/local-storage.js +0 -455
- package/dist/src/modules/passwords.d.ts +0 -50
- package/dist/src/modules/passwords.js +0 -198
- package/dist/src/modules/search-engines.d.ts +0 -21
- package/dist/src/modules/search-engines.js +0 -38
- package/dist/src/modules/top-sites.d.ts +0 -17
- package/dist/src/modules/top-sites.js +0 -33
- package/src/native/build.bat +0 -31
- package/src/native/chrome_abe_decrypt.c +0 -336
- package/src/native/chrome_abe_decrypt.exe +0 -0
- /package/{bin → dist}/chrome_abe_decrypt.exe +0 -0
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { deriveEncryptionKey, clearKeychainCache } from "../keychain.js";
|
|
4
|
-
import { decryptValueBuffer } from "../crypto.js";
|
|
5
|
-
import { getActiveBrowser } from "../browsers.js";
|
|
6
|
-
/**
|
|
7
|
-
* macOS backend.
|
|
8
|
-
*
|
|
9
|
-
* Key source: the active browser's "<Browser> Safe Storage" password from the
|
|
10
|
-
* login Keychain (e.g. "Chrome Safe Storage", "Microsoft Edge Safe Storage"),
|
|
11
|
-
* stretched with PBKDF2 (1003 iterations) — implemented in `core/keychain.ts`.
|
|
12
|
-
* Cipher: AES-128-CBC with a fixed 16-space IV — implemented in `core/crypto.ts`.
|
|
13
|
-
* The browser only changes *which* Keychain item and data dir are used; the
|
|
14
|
-
* crypto is identical across all Chromium forks.
|
|
15
|
-
*/
|
|
16
|
-
export const darwin = {
|
|
17
|
-
name: "darwin",
|
|
18
|
-
getDefaultChromeDir(browser = getActiveBrowser()) {
|
|
19
|
-
return join(homedir(), "Library", "Application Support", ...browser.dirs.darwin.split("/"));
|
|
20
|
-
},
|
|
21
|
-
getDecryptionKey(_chromeDir, browser = getActiveBrowser()) {
|
|
22
|
-
// The Keychain item is global to the machine, so chromeDir is irrelevant here.
|
|
23
|
-
return deriveEncryptionKey(browser.safeStorageLabel);
|
|
24
|
-
},
|
|
25
|
-
decrypt(blob, key) {
|
|
26
|
-
return decryptValueBuffer(blob, key);
|
|
27
|
-
},
|
|
28
|
-
clearCache() {
|
|
29
|
-
clearKeychainCache();
|
|
30
|
-
},
|
|
31
|
-
};
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Platform selection + facades.
|
|
3
|
-
*
|
|
4
|
-
* This is the ONLY place that reads `process.platform`. Everything else in the
|
|
5
|
-
* codebase imports the facades below (`getPlatformChromeDir`, `deriveEncryptionKey`,
|
|
6
|
-
* `decryptValue`, `decryptValueBuffer`, `clearKeyCache`) and stays OS-agnostic.
|
|
7
|
-
*/
|
|
8
|
-
import type { ChromePlatform } from "./types.js";
|
|
9
|
-
import { type BrowserDescriptor } from "../browsers.js";
|
|
10
|
-
export type { ChromePlatform } from "./types.js";
|
|
11
|
-
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "../browsers.js";
|
|
12
|
-
export { KeychainAccessError, isKeychainAccessDenied, type KeychainErrorKind, } from "../keychain.js";
|
|
13
|
-
/**
|
|
14
|
-
* Returns the backend for the current OS, cached after first call.
|
|
15
|
-
* @throws On an unsupported platform.
|
|
16
|
-
*/
|
|
17
|
-
export declare function selectPlatform(): ChromePlatform;
|
|
18
|
-
/** Default Chrome User Data directory for the current OS. */
|
|
19
|
-
export declare function getPlatformChromeDir(browser?: string | BrowserDescriptor): string;
|
|
20
|
-
/**
|
|
21
|
-
* Returns the symmetric key for decrypting this profile's secrets.
|
|
22
|
-
* @param chromeDir - Needed on Windows, where the key lives in `Local State`.
|
|
23
|
-
*/
|
|
24
|
-
export declare function deriveEncryptionKey(chromeDir?: string, browser?: string | BrowserDescriptor): Buffer;
|
|
25
|
-
/** Decrypts a Chrome blob and returns the plaintext bytes (or a placeholder string). */
|
|
26
|
-
export declare function decryptValueBuffer(blob: Buffer | Uint8Array | null | undefined, key: Buffer): Buffer | string;
|
|
27
|
-
/** Decrypts a Chrome blob and returns the plaintext as a UTF-8 string. */
|
|
28
|
-
export declare function decryptValue(blob: Buffer | Uint8Array | null | undefined, key: Buffer): string;
|
|
29
|
-
/** Clears the active backend's cached key material. */
|
|
30
|
-
export declare function clearKeyCache(): void;
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { darwin } from "./darwin.js";
|
|
2
|
-
import { win32 } from "./win32.js";
|
|
3
|
-
import { linux } from "./linux.js";
|
|
4
|
-
import { getActiveBrowser, requireBrowser } from "../browsers.js";
|
|
5
|
-
// Browser selection is orthogonal to platform selection: the backends above
|
|
6
|
-
// read the active browser's data dir + Safe Storage label from the registry.
|
|
7
|
-
// Re-exported here so the rest of the codebase has one import site for both
|
|
8
|
-
// "which OS" and "which browser".
|
|
9
|
-
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, } from "../browsers.js";
|
|
10
|
-
// Keychain access failures (macOS) surface a typed error so the CLI can show a
|
|
11
|
-
// clear "No keychain access" message instead of a generic failure.
|
|
12
|
-
export { KeychainAccessError, isKeychainAccessDenied, } from "../keychain.js";
|
|
13
|
-
let _active = null;
|
|
14
|
-
/**
|
|
15
|
-
* Returns the backend for the current OS, cached after first call.
|
|
16
|
-
* @throws On an unsupported platform.
|
|
17
|
-
*/
|
|
18
|
-
export function selectPlatform() {
|
|
19
|
-
if (_active)
|
|
20
|
-
return _active;
|
|
21
|
-
switch (process.platform) {
|
|
22
|
-
case "darwin":
|
|
23
|
-
_active = darwin;
|
|
24
|
-
break;
|
|
25
|
-
case "win32":
|
|
26
|
-
_active = win32;
|
|
27
|
-
break;
|
|
28
|
-
case "linux":
|
|
29
|
-
_active = linux;
|
|
30
|
-
break;
|
|
31
|
-
default:
|
|
32
|
-
throw new Error(`Unsupported platform: ${process.platform}. ` +
|
|
33
|
-
"chrome-tools supports macOS, Windows, and Linux.");
|
|
34
|
-
}
|
|
35
|
-
return _active;
|
|
36
|
-
}
|
|
37
|
-
/** Default Chrome User Data directory for the current OS. */
|
|
38
|
-
export function getPlatformChromeDir(browser) {
|
|
39
|
-
return selectPlatform().getDefaultChromeDir(resolveTargetBrowser(browser));
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Returns the symmetric key for decrypting this profile's secrets.
|
|
43
|
-
* @param chromeDir - Needed on Windows, where the key lives in `Local State`.
|
|
44
|
-
*/
|
|
45
|
-
export function deriveEncryptionKey(chromeDir, browser) {
|
|
46
|
-
return selectPlatform().getDecryptionKey(chromeDir, resolveTargetBrowser(browser));
|
|
47
|
-
}
|
|
48
|
-
/** Decrypts a Chrome blob and returns the plaintext bytes (or a placeholder string). */
|
|
49
|
-
export function decryptValueBuffer(blob, key) {
|
|
50
|
-
if (!blob || blob.length === 0) {
|
|
51
|
-
return Buffer.alloc(0);
|
|
52
|
-
}
|
|
53
|
-
const buf = Buffer.isBuffer(blob) ? blob : Buffer.from(blob);
|
|
54
|
-
return selectPlatform().decrypt(buf, key);
|
|
55
|
-
}
|
|
56
|
-
/** Decrypts a Chrome blob and returns the plaintext as a UTF-8 string. */
|
|
57
|
-
export function decryptValue(blob, key) {
|
|
58
|
-
const decrypted = decryptValueBuffer(blob, key);
|
|
59
|
-
return Buffer.isBuffer(decrypted) ? decrypted.toString("utf-8") : decrypted;
|
|
60
|
-
}
|
|
61
|
-
/** Clears the active backend's cached key material. */
|
|
62
|
-
export function clearKeyCache() {
|
|
63
|
-
selectPlatform().clearCache();
|
|
64
|
-
}
|
|
65
|
-
function resolveTargetBrowser(browser) {
|
|
66
|
-
if (!browser)
|
|
67
|
-
return getActiveBrowser();
|
|
68
|
-
return typeof browser === "string" ? requireBrowser(browser) : browser;
|
|
69
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { ChromePlatform } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Linux backend.
|
|
4
|
-
*
|
|
5
|
-
* Key source: the active browser's "Safe Storage" password from the Secret
|
|
6
|
-
* Service (gnome-keyring / KWallet) via `secret-tool`, or the hardcoded
|
|
7
|
-
* "peanuts" fallback, stretched with PBKDF2 (1 iteration).
|
|
8
|
-
* Cipher: AES-128-CBC with a fixed 16-space IV (same primitive as macOS).
|
|
9
|
-
*/
|
|
10
|
-
export declare const linux: ChromePlatform;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { pbkdf2Sync } from "node:crypto";
|
|
5
|
-
import { decryptValueBuffer } from "../crypto.js";
|
|
6
|
-
import { getActiveBrowser } from "../browsers.js";
|
|
7
|
-
import { getKeychainPasswordProvider } from "../keychain.js";
|
|
8
|
-
// Linux uses the same CBC scheme as macOS but with a single PBKDF2 iteration.
|
|
9
|
-
const PBKDF2_ITERATIONS = 1;
|
|
10
|
-
const PBKDF2_KEY_LENGTH = 16;
|
|
11
|
-
const PBKDF2_SALT = Buffer.from("saltysalt");
|
|
12
|
-
// Chrome's hardcoded fallback when no secret service (gnome-keyring/kwallet) is
|
|
13
|
-
// available — yields the "Basic" (effectively unencrypted) storage scheme.
|
|
14
|
-
const FALLBACK_PASSWORD = "peanuts";
|
|
15
|
-
// Keyed by Secret Service `application` attribute so different browsers cache
|
|
16
|
-
// independently (e.g. under `--all-browsers`).
|
|
17
|
-
const _cachedKeys = new Map();
|
|
18
|
-
/** Looks up the "Safe Storage" password via libsecret, falling back to "peanuts". */
|
|
19
|
-
function getStoragePassword(application) {
|
|
20
|
-
const provider = getKeychainPasswordProvider();
|
|
21
|
-
if (provider) {
|
|
22
|
-
return provider(application);
|
|
23
|
-
}
|
|
24
|
-
try {
|
|
25
|
-
const out = execFileSync("secret-tool", ["lookup", "application", application], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
26
|
-
if (out)
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
// secret-tool missing, or no keyring / no entry — fall through to default.
|
|
31
|
-
}
|
|
32
|
-
return FALLBACK_PASSWORD;
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Linux backend.
|
|
36
|
-
*
|
|
37
|
-
* Key source: the active browser's "Safe Storage" password from the Secret
|
|
38
|
-
* Service (gnome-keyring / KWallet) via `secret-tool`, or the hardcoded
|
|
39
|
-
* "peanuts" fallback, stretched with PBKDF2 (1 iteration).
|
|
40
|
-
* Cipher: AES-128-CBC with a fixed 16-space IV (same primitive as macOS).
|
|
41
|
-
*/
|
|
42
|
-
export const linux = {
|
|
43
|
-
name: "linux",
|
|
44
|
-
getDefaultChromeDir(browser = getActiveBrowser()) {
|
|
45
|
-
const configHome = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
46
|
-
return join(configHome, ...browser.dirs.linux.split("/"));
|
|
47
|
-
},
|
|
48
|
-
getDecryptionKey(_chromeDir, browser = getActiveBrowser()) {
|
|
49
|
-
const application = browser.linuxSecretApp || "chrome";
|
|
50
|
-
const cached = _cachedKeys.get(application);
|
|
51
|
-
if (cached !== undefined) {
|
|
52
|
-
return cached;
|
|
53
|
-
}
|
|
54
|
-
const key = pbkdf2Sync(getStoragePassword(application), PBKDF2_SALT, PBKDF2_ITERATIONS, PBKDF2_KEY_LENGTH, "sha1");
|
|
55
|
-
_cachedKeys.set(application, key);
|
|
56
|
-
return key;
|
|
57
|
-
},
|
|
58
|
-
decrypt(blob, key) {
|
|
59
|
-
return decryptValueBuffer(blob, key);
|
|
60
|
-
},
|
|
61
|
-
clearCache() {
|
|
62
|
-
_cachedKeys.clear();
|
|
63
|
-
},
|
|
64
|
-
};
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { BrowserDescriptor } from "../browsers.js";
|
|
2
|
-
/**
|
|
3
|
-
* The platform-specific backend contract.
|
|
4
|
-
*
|
|
5
|
-
* Everything that differs between operating systems is bundled here because the
|
|
6
|
-
* three concerns are coupled — a Windows key only works with the Windows cipher,
|
|
7
|
-
* etc. Each backend (darwin/win32/linux) implements this once; the rest of the
|
|
8
|
-
* codebase stays platform-agnostic and talks to the selected backend through the
|
|
9
|
-
* facades in `./index.ts`.
|
|
10
|
-
*/
|
|
11
|
-
export interface ChromePlatform {
|
|
12
|
-
/** Node platform identifier this backend handles (e.g. "darwin", "win32", "linux"). */
|
|
13
|
-
readonly name: string;
|
|
14
|
-
/** Absolute path to Chrome's User Data directory for this OS. */
|
|
15
|
-
getDefaultChromeDir(browser?: BrowserDescriptor): string;
|
|
16
|
-
/**
|
|
17
|
-
* Returns the symmetric key used to decrypt this profile's secrets, ready to
|
|
18
|
-
* hand to {@link decrypt}. May read the OS key store and is cached internally.
|
|
19
|
-
*
|
|
20
|
-
* @param chromeDir - User Data dir, needed on platforms (Windows) where the
|
|
21
|
-
* key lives inside `Local State`. Defaults to {@link getDefaultChromeDir}.
|
|
22
|
-
* @throws An actionable Error if the key cannot be obtained.
|
|
23
|
-
*/
|
|
24
|
-
getDecryptionKey(chromeDir?: string, browser?: BrowserDescriptor): Buffer;
|
|
25
|
-
/**
|
|
26
|
-
* Returns the App-Bound Encryption key for Chrome 127+ on Windows.
|
|
27
|
-
* Only the win32 backend implements this; others return `undefined`.
|
|
28
|
-
*
|
|
29
|
-
* @param chromeDir - User Data dir containing the `Local State` file.
|
|
30
|
-
* @param browser - Browser to target (needed to select the COM CLSID).
|
|
31
|
-
* @returns The decrypted 32-byte AES key, or `undefined` if ABE is
|
|
32
|
-
* unavailable or unsupported on this platform.
|
|
33
|
-
*/
|
|
34
|
-
getAppBoundKey?(chromeDir?: string, browser?: BrowserDescriptor): Buffer | undefined;
|
|
35
|
-
/**
|
|
36
|
-
* Decrypts a single Chrome `v10`/`v11`/`v20` blob using this platform's
|
|
37
|
-
* scheme (AES-128-CBC on macOS/Linux, AES-256-GCM on Windows).
|
|
38
|
-
*
|
|
39
|
-
* @returns The plaintext bytes, or a `<...>` placeholder string on a
|
|
40
|
-
* recognized but undecryptable blob (never throws).
|
|
41
|
-
*/
|
|
42
|
-
decrypt(blob: Buffer, key: Buffer): Buffer | string;
|
|
43
|
-
/** Clears any cached key material. Useful for tests / re-authentication. */
|
|
44
|
-
clearCache(): void;
|
|
45
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ChromePlatform } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Windows backend.
|
|
4
|
-
*
|
|
5
|
-
* Key source: `os_crypt.encrypted_key` in Local State — base64, "DPAPI"-prefixed,
|
|
6
|
-
* unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
|
|
7
|
-
* Cipher: AES-256-GCM (nonce + tag carried in each blob).
|
|
8
|
-
*
|
|
9
|
-
* Chrome 127+ adds `os_crypt.app_bound_encrypted_key` — base64, "APPB"-prefixed.
|
|
10
|
-
* After DPAPI unprotection, the result is fed to Chrome's IElevator COM service
|
|
11
|
-
* via the native helper to obtain the real 32-byte AES key for "v20" blobs.
|
|
12
|
-
*/
|
|
13
|
-
export declare const win32: ChromePlatform;
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
import { decryptValueBufferGcm, decryptValueBufferGcmV20 } from "../crypto.js";
|
|
5
|
-
import { getActiveBrowser } from "../browsers.js";
|
|
6
|
-
// Chrome base64-encodes the DPAPI-protected AES key in Local State, prefixed
|
|
7
|
-
// with the ASCII bytes "DPAPI".
|
|
8
|
-
const DPAPI_PREFIX = Buffer.from("DPAPI", "ascii");
|
|
9
|
-
// Chrome 127+ uses App-Bound Encryption. The key in Local State is prefixed
|
|
10
|
-
// with "APPB" and requires the IElevator COM service to decrypt.
|
|
11
|
-
const APPB_PREFIX = Buffer.from("APPB", "ascii");
|
|
12
|
-
// Keyed by User Data dir so different browsers (each with its own Local State)
|
|
13
|
-
// cache independently (e.g. under `--all-browsers`).
|
|
14
|
-
const _cachedKeys = new Map();
|
|
15
|
-
const _cachedABEKeys = new Map();
|
|
16
|
-
// Maps browser ids to the COM service identifier used by the native helper.
|
|
17
|
-
const ABE_BROWSER_MAP = {
|
|
18
|
-
chrome: "chrome",
|
|
19
|
-
"chrome-beta": "chrome-beta",
|
|
20
|
-
"chrome-canary": "chrome",
|
|
21
|
-
chromium: "chrome",
|
|
22
|
-
brave: "brave",
|
|
23
|
-
edge: "edge",
|
|
24
|
-
vivaldi: "chrome",
|
|
25
|
-
comet: "chrome",
|
|
26
|
-
opera: "chrome",
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Unprotects a DPAPI blob for the current user.
|
|
30
|
-
*
|
|
31
|
-
* Node has no binding for `CryptUnprotectData`, so we shell out to PowerShell's
|
|
32
|
-
* `ProtectedData.Unprotect`. This needs no native build step. For a faster,
|
|
33
|
-
* dependency-light alternative, swap this for the optional `win-dpapi` addon.
|
|
34
|
-
*/
|
|
35
|
-
function dpapiDecrypt(encrypted) {
|
|
36
|
-
const script = [
|
|
37
|
-
"$ErrorActionPreference='Stop';",
|
|
38
|
-
"Add-Type -AssemblyName System.Security;",
|
|
39
|
-
"$b=[Console]::In.ReadToEnd().Trim();",
|
|
40
|
-
"$enc=[Convert]::FromBase64String($b);",
|
|
41
|
-
"$dec=[System.Security.Cryptography.ProtectedData]::Unprotect($enc,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);",
|
|
42
|
-
"[Console]::Out.Write([Convert]::ToBase64String($dec));",
|
|
43
|
-
].join(" ");
|
|
44
|
-
let out;
|
|
45
|
-
try {
|
|
46
|
-
out = execFileSync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { input: encrypted.toString("base64"), encoding: "utf-8" });
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
throw new Error("DPAPI decryption via PowerShell failed.\n" +
|
|
50
|
-
"The encrypted_key can only be unprotected by the same Windows user that " +
|
|
51
|
-
"created it. Run as that user on the same machine.\n" +
|
|
52
|
-
`(powershell: ${err?.message ?? err})`);
|
|
53
|
-
}
|
|
54
|
-
return Buffer.from(out.trim(), "base64");
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Resolves the Local State directory from a profile/chromeDir path.
|
|
58
|
-
* `profilePath` may point directly at .../Default. Local State belongs to
|
|
59
|
-
* the parent User Data directory.
|
|
60
|
-
*/
|
|
61
|
-
function resolveLocalStateDir(requestedDir) {
|
|
62
|
-
const parentDir = dirname(requestedDir);
|
|
63
|
-
return existsSync(join(requestedDir, "Local State"))
|
|
64
|
-
? requestedDir
|
|
65
|
-
: parentDir !== requestedDir && existsSync(join(parentDir, "Local State"))
|
|
66
|
-
? parentDir
|
|
67
|
-
: requestedDir;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Finds the chrome_abe_decrypt helper executable.
|
|
71
|
-
* Searches in order:
|
|
72
|
-
* 1. Next to this module (dist/src/core/platform/)
|
|
73
|
-
* 2. In the package bin/ directory
|
|
74
|
-
* 3. In src/native/ (development)
|
|
75
|
-
* 4. On PATH
|
|
76
|
-
*/
|
|
77
|
-
function findABEHelper() {
|
|
78
|
-
const base = import.meta.dirname ?? "";
|
|
79
|
-
const candidates = [
|
|
80
|
-
join(base, "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
|
|
81
|
-
join(base, "..", "..", "..", "..", "bin", "chrome_abe_decrypt.exe"),
|
|
82
|
-
join(base, "..", "..", "..", "..", "src", "native", "chrome_abe_decrypt.exe"),
|
|
83
|
-
];
|
|
84
|
-
for (const p of candidates) {
|
|
85
|
-
if (existsSync(p))
|
|
86
|
-
return p;
|
|
87
|
-
}
|
|
88
|
-
// Try on PATH
|
|
89
|
-
try {
|
|
90
|
-
execFileSync("where", ["chrome_abe_decrypt.exe"], { encoding: "utf-8", stdio: "pipe" });
|
|
91
|
-
return "chrome_abe_decrypt.exe";
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
let _abeHelperPath;
|
|
98
|
-
let _abeWarningShown = false;
|
|
99
|
-
/**
|
|
100
|
-
* Decrypts an app_bound_encrypted_key using Chrome's IElevator COM service
|
|
101
|
-
* via the native helper binary.
|
|
102
|
-
*
|
|
103
|
-
* The key from Local State is already DPAPI-decrypted at this point. This
|
|
104
|
-
* function passes it to the COM service for the second decryption layer.
|
|
105
|
-
*/
|
|
106
|
-
function abeDecrypt(dpapiDecrypted, browserId) {
|
|
107
|
-
if (_abeHelperPath === undefined) {
|
|
108
|
-
_abeHelperPath = findABEHelper();
|
|
109
|
-
}
|
|
110
|
-
if (!_abeHelperPath) {
|
|
111
|
-
if (!_abeWarningShown) {
|
|
112
|
-
_abeWarningShown = true;
|
|
113
|
-
process.stderr.write("[chrome-tools] chrome_abe_decrypt.exe not found. Chrome 127+ v20-encrypted\n" +
|
|
114
|
-
"cookies/passwords cannot be decrypted. See src/native/README for build instructions.\n");
|
|
115
|
-
}
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
const comBrowser = ABE_BROWSER_MAP[browserId] || "chrome";
|
|
119
|
-
try {
|
|
120
|
-
const out = execFileSync(_abeHelperPath, [comBrowser], { input: dpapiDecrypted.toString("base64"), encoding: "utf-8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] });
|
|
121
|
-
return Buffer.from(out.trim(), "base64");
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Windows backend.
|
|
129
|
-
*
|
|
130
|
-
* Key source: `os_crypt.encrypted_key` in Local State — base64, "DPAPI"-prefixed,
|
|
131
|
-
* unprotected via DPAPI into a raw 32-byte AES-256 key (no PBKDF2 on Windows).
|
|
132
|
-
* Cipher: AES-256-GCM (nonce + tag carried in each blob).
|
|
133
|
-
*
|
|
134
|
-
* Chrome 127+ adds `os_crypt.app_bound_encrypted_key` — base64, "APPB"-prefixed.
|
|
135
|
-
* After DPAPI unprotection, the result is fed to Chrome's IElevator COM service
|
|
136
|
-
* via the native helper to obtain the real 32-byte AES key for "v20" blobs.
|
|
137
|
-
*/
|
|
138
|
-
export const win32 = {
|
|
139
|
-
name: "win32",
|
|
140
|
-
getDefaultChromeDir(browser = getActiveBrowser()) {
|
|
141
|
-
const roaming = browser.dirs.win32Base === "roaming";
|
|
142
|
-
const appData = roaming
|
|
143
|
-
? process.env.APPDATA ||
|
|
144
|
-
(process.env.USERPROFILE
|
|
145
|
-
? join(process.env.USERPROFILE, "AppData", "Roaming")
|
|
146
|
-
: "")
|
|
147
|
-
: process.env.LOCALAPPDATA ||
|
|
148
|
-
(process.env.USERPROFILE
|
|
149
|
-
? join(process.env.USERPROFILE, "AppData", "Local")
|
|
150
|
-
: "");
|
|
151
|
-
if (!appData) {
|
|
152
|
-
throw new Error("Could not resolve the Windows application-data directory.");
|
|
153
|
-
}
|
|
154
|
-
return join(appData, ...browser.dirs.win32.split("/"));
|
|
155
|
-
},
|
|
156
|
-
getDecryptionKey(chromeDir, browser = getActiveBrowser()) {
|
|
157
|
-
const requestedDir = chromeDir || this.getDefaultChromeDir(browser);
|
|
158
|
-
const dir = resolveLocalStateDir(requestedDir);
|
|
159
|
-
const cached = _cachedKeys.get(dir);
|
|
160
|
-
if (cached !== undefined) {
|
|
161
|
-
return cached;
|
|
162
|
-
}
|
|
163
|
-
const localStatePath = join(dir, "Local State");
|
|
164
|
-
if (!existsSync(localStatePath)) {
|
|
165
|
-
throw new Error(`Could not find Chrome's "Local State" at ${localStatePath}.\n` +
|
|
166
|
-
"Run Google Chrome at least once so it creates the encryption key.");
|
|
167
|
-
}
|
|
168
|
-
let encryptedKeyB64;
|
|
169
|
-
try {
|
|
170
|
-
const raw = readFileSync(localStatePath, "utf-8");
|
|
171
|
-
encryptedKeyB64 = JSON.parse(raw)?.os_crypt?.encrypted_key;
|
|
172
|
-
}
|
|
173
|
-
catch (err) {
|
|
174
|
-
throw new Error(`Failed to read os_crypt.encrypted_key from Local State: ${err.message}`);
|
|
175
|
-
}
|
|
176
|
-
if (!encryptedKeyB64) {
|
|
177
|
-
throw new Error("No os_crypt.encrypted_key found in Local State.\n" +
|
|
178
|
-
"This profile may predate Chrome's encrypted-key scheme.");
|
|
179
|
-
}
|
|
180
|
-
const encryptedKey = Buffer.from(encryptedKeyB64, "base64");
|
|
181
|
-
if (!encryptedKey.subarray(0, DPAPI_PREFIX.length).equals(DPAPI_PREFIX)) {
|
|
182
|
-
throw new Error('Unexpected encrypted_key format (missing "DPAPI" prefix).');
|
|
183
|
-
}
|
|
184
|
-
const key = dpapiDecrypt(encryptedKey.subarray(DPAPI_PREFIX.length));
|
|
185
|
-
_cachedKeys.set(dir, key);
|
|
186
|
-
return key;
|
|
187
|
-
},
|
|
188
|
-
getAppBoundKey(chromeDir, browser = getActiveBrowser()) {
|
|
189
|
-
const requestedDir = chromeDir || this.getDefaultChromeDir(browser);
|
|
190
|
-
const dir = resolveLocalStateDir(requestedDir);
|
|
191
|
-
const cached = _cachedABEKeys.get(dir);
|
|
192
|
-
if (cached !== undefined) {
|
|
193
|
-
return cached;
|
|
194
|
-
}
|
|
195
|
-
const localStatePath = join(dir, "Local State");
|
|
196
|
-
if (!existsSync(localStatePath))
|
|
197
|
-
return undefined;
|
|
198
|
-
let appBoundKeyB64;
|
|
199
|
-
try {
|
|
200
|
-
const raw = readFileSync(localStatePath, "utf-8");
|
|
201
|
-
appBoundKeyB64 = JSON.parse(raw)?.os_crypt?.app_bound_encrypted_key;
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
if (!appBoundKeyB64)
|
|
207
|
-
return undefined;
|
|
208
|
-
const appBoundKey = Buffer.from(appBoundKeyB64, "base64");
|
|
209
|
-
if (!appBoundKey.subarray(0, APPB_PREFIX.length).equals(APPB_PREFIX)) {
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
// Step 1: DPAPI-decrypt the key (strip the "APPB" prefix first)
|
|
213
|
-
let dpapiResult;
|
|
214
|
-
try {
|
|
215
|
-
dpapiResult = dpapiDecrypt(appBoundKey.subarray(APPB_PREFIX.length));
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return undefined;
|
|
219
|
-
}
|
|
220
|
-
// Step 2: Use the IElevator COM service to decrypt the ABE layer
|
|
221
|
-
const abeKey = abeDecrypt(dpapiResult, browser.id);
|
|
222
|
-
if (!abeKey)
|
|
223
|
-
return undefined;
|
|
224
|
-
// The COM service returns a key that may have a 4-byte header; the last 32
|
|
225
|
-
// bytes are the actual AES-256 key. If the result is exactly 32 bytes, use
|
|
226
|
-
// it as-is.
|
|
227
|
-
const key = abeKey.length > 32 ? abeKey.subarray(abeKey.length - 32) : abeKey;
|
|
228
|
-
_cachedABEKeys.set(dir, key);
|
|
229
|
-
return key;
|
|
230
|
-
},
|
|
231
|
-
decrypt(blob, key) {
|
|
232
|
-
const prefix = blob.subarray(0, 3).toString("utf-8");
|
|
233
|
-
// v20 = App-Bound Encryption (Chrome 127+)
|
|
234
|
-
if (prefix === "v20") {
|
|
235
|
-
// Try to get the ABE key for this profile
|
|
236
|
-
const abeKey = this.getAppBoundKey?.();
|
|
237
|
-
if (!abeKey) {
|
|
238
|
-
return "<v20 app-bound encryption - requires chrome_abe_decrypt.exe>";
|
|
239
|
-
}
|
|
240
|
-
return decryptValueBufferGcmV20(blob, abeKey);
|
|
241
|
-
}
|
|
242
|
-
// v10/v11 = standard DPAPI + AES-256-GCM
|
|
243
|
-
return decryptValueBufferGcm(blob, key);
|
|
244
|
-
},
|
|
245
|
-
clearCache() {
|
|
246
|
-
_cachedKeys.clear();
|
|
247
|
-
_cachedABEKeys.clear();
|
|
248
|
-
},
|
|
249
|
-
};
|
package/dist/src/core/sql.d.ts
DELETED
package/dist/src/core/sql.js
DELETED
package/dist/src/index.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* chrome-tools — Public API
|
|
3
|
-
*
|
|
4
|
-
* This module re-exports all feature modules for use as a Node.js library.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* import { getPasswords, getBookmarks, discoverProfiles } from 'chrome-tools';
|
|
8
|
-
*
|
|
9
|
-
* const passwords = getPasswords({ profile: 'Default' });
|
|
10
|
-
* const bookmarks = getBookmarks({ search: 'github' });
|
|
11
|
-
* const profiles = discoverProfiles();
|
|
12
|
-
*/
|
|
13
|
-
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, detectInstalledBrowsers, type DiscoveredProfile, type ProfileMetadata, type ProfileState, type ResolveProfilesOptions, type InstalledBrowser, } from "./core/chrome.js";
|
|
14
|
-
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, type BrowserId, type BrowserDescriptor, } from "./core/browsers.js";
|
|
15
|
-
export { selectPlatform, getPlatformChromeDir, deriveEncryptionKey, decryptValue, decryptValueBuffer, clearKeyCache, type ChromePlatform, } from "./core/platform/index.js";
|
|
16
|
-
export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, setKeychainPasswordProvider, getKeychainPasswordProvider, type KeychainPasswordProvider, } from "./core/keychain.js";
|
|
17
|
-
export { chromeTimeToDate, } from "./core/crypto.js";
|
|
18
|
-
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
19
|
-
export { getPasswords } from "./modules/passwords.js";
|
|
20
|
-
export { getBookmarks } from "./modules/bookmarks.js";
|
|
21
|
-
export { getHistory, getSearchTerms } from "./modules/history.js";
|
|
22
|
-
export { getCookies, type CookieEntry, type CookieOptions } from "./modules/cookies.js";
|
|
23
|
-
export { getLocalStorage, type LocalStorageEntry, type LocalStorageOptions } from "./modules/local-storage.js";
|
|
24
|
-
export { getAutofillEntries, getCreditCards, getAddresses } from "./modules/autofill.js";
|
|
25
|
-
export { getDownloads } from "./modules/downloads.js";
|
|
26
|
-
export { getExtensions } from "./modules/extensions.js";
|
|
27
|
-
export { getSearchEngines } from "./modules/search-engines.js";
|
|
28
|
-
export { getTopSites } from "./modules/top-sites.js";
|
package/dist/src/index.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* chrome-tools — Public API
|
|
3
|
-
*
|
|
4
|
-
* This module re-exports all feature modules for use as a Node.js library.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* import { getPasswords, getBookmarks, discoverProfiles } from 'chrome-tools';
|
|
8
|
-
*
|
|
9
|
-
* const passwords = getPasswords({ profile: 'Default' });
|
|
10
|
-
* const bookmarks = getBookmarks({ search: 'github' });
|
|
11
|
-
* const profiles = discoverProfiles();
|
|
12
|
-
*/
|
|
13
|
-
// Core utilities
|
|
14
|
-
export { getDefaultChromeDir, discoverProfiles, getProfileMetadata, getProfileStates, resolveProfilePath, resolveTargetProfiles, detectInstalledBrowsers, } from "./core/chrome.js";
|
|
15
|
-
// Browser registry & selection (all Chromium-based browsers)
|
|
16
|
-
export { BROWSERS, listBrowsers, resolveBrowser, requireBrowser, getActiveBrowser, setActiveBrowser, } from "./core/browsers.js";
|
|
17
|
-
// Platform-aware decryption (macOS / Windows / Linux)
|
|
18
|
-
export { selectPlatform, getPlatformChromeDir, deriveEncryptionKey, decryptValue, decryptValueBuffer, clearKeyCache, } from "./core/platform/index.js";
|
|
19
|
-
// macOS Keychain helpers (macOS-only). `getKeychainPassword` takes a Safe
|
|
20
|
-
// Storage service name; `getChromeKeychainPassword` is the Chrome-specific alias.
|
|
21
|
-
export { getKeychainPassword, getChromeKeychainPassword, clearKeychainCache, setKeychainPasswordProvider, getKeychainPasswordProvider, } from "./core/keychain.js";
|
|
22
|
-
export { chromeTimeToDate, } from "./core/crypto.js";
|
|
23
|
-
export { queryDatabase, tableExists, } from "./core/db.js";
|
|
24
|
-
// Feature modules
|
|
25
|
-
export { getPasswords } from "./modules/passwords.js";
|
|
26
|
-
export { getBookmarks } from "./modules/bookmarks.js";
|
|
27
|
-
export { getHistory, getSearchTerms } from "./modules/history.js";
|
|
28
|
-
export { getCookies } from "./modules/cookies.js";
|
|
29
|
-
export { getLocalStorage } from "./modules/local-storage.js";
|
|
30
|
-
export { getAutofillEntries, getCreditCards, getAddresses } from "./modules/autofill.js";
|
|
31
|
-
export { getDownloads } from "./modules/downloads.js";
|
|
32
|
-
export { getExtensions } from "./modules/extensions.js";
|
|
33
|
-
export { getSearchEngines } from "./modules/search-engines.js";
|
|
34
|
-
export { getTopSites } from "./modules/top-sites.js";
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { type ResolveProfilesOptions } from "../core/chrome.js";
|
|
2
|
-
export interface AutofillOptions extends ResolveProfilesOptions {
|
|
3
|
-
search?: string;
|
|
4
|
-
limit?: number;
|
|
5
|
-
}
|
|
6
|
-
export interface AutofillEntry {
|
|
7
|
-
profile: string;
|
|
8
|
-
name: string;
|
|
9
|
-
value: string;
|
|
10
|
-
dateCreated: string;
|
|
11
|
-
dateLastUsed: string;
|
|
12
|
-
count: number;
|
|
13
|
-
}
|
|
14
|
-
export interface CreditCardOptions extends ResolveProfilesOptions {
|
|
15
|
-
decryptionKey?: Buffer;
|
|
16
|
-
}
|
|
17
|
-
export interface CreditCardEntry {
|
|
18
|
-
profile: string;
|
|
19
|
-
guid: string;
|
|
20
|
-
nameOnCard: string;
|
|
21
|
-
expirationMonth: number;
|
|
22
|
-
expirationYear: number;
|
|
23
|
-
cardNumber: string;
|
|
24
|
-
nickname: string;
|
|
25
|
-
origin: string;
|
|
26
|
-
useCount: number;
|
|
27
|
-
dateModified: string;
|
|
28
|
-
}
|
|
29
|
-
export interface AddressOptions extends ResolveProfilesOptions {
|
|
30
|
-
}
|
|
31
|
-
export interface AddressEntry {
|
|
32
|
-
profile: string;
|
|
33
|
-
guid: string;
|
|
34
|
-
fullName: string;
|
|
35
|
-
email: string;
|
|
36
|
-
phone: string;
|
|
37
|
-
companyName: string;
|
|
38
|
-
streetAddress: string;
|
|
39
|
-
city: string;
|
|
40
|
-
state: string;
|
|
41
|
-
zipcode: string;
|
|
42
|
-
countryCode: string;
|
|
43
|
-
origin: string;
|
|
44
|
-
useCount: number;
|
|
45
|
-
dateModified: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Retrieves autofill entries from Chrome's Web Data database.
|
|
49
|
-
*
|
|
50
|
-
* @param options - Filtering and targeting options.
|
|
51
|
-
* @returns Array of autofill entries.
|
|
52
|
-
*/
|
|
53
|
-
export declare function getAutofillEntries({ chromeDir, profilePath, profile, browser, search, limit, }?: AutofillOptions): AutofillEntry[];
|
|
54
|
-
/**
|
|
55
|
-
* Retrieves saved credit cards from Chrome's Web Data database.
|
|
56
|
-
* Card numbers are encrypted and will be decrypted.
|
|
57
|
-
*
|
|
58
|
-
* @param options - Targeting options.
|
|
59
|
-
* @returns Array of credit card entries.
|
|
60
|
-
*/
|
|
61
|
-
export declare function getCreditCards({ chromeDir, profilePath, profile, browser, decryptionKey, }?: CreditCardOptions): CreditCardEntry[];
|
|
62
|
-
/**
|
|
63
|
-
* Retrieves saved addresses from Chrome's Web Data database.
|
|
64
|
-
*
|
|
65
|
-
* @param options - Targeting options.
|
|
66
|
-
* @returns Array of address entries.
|
|
67
|
-
*/
|
|
68
|
-
export declare function getAddresses({ chromeDir, profilePath, profile, browser }?: AddressOptions): AddressEntry[];
|