diff --git a/.gitignore b/.gitignore index 4c1fb8c..9cf0df4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules/ !.yarn/releases !.yarn/sdks !.yarn/versions +coverage/ \ No newline at end of file diff --git a/app/lib.js b/app/lib.js index cd7e495..17b918b 100644 --- a/app/lib.js +++ b/app/lib.js @@ -88,27 +88,103 @@ function extractVaultFromFile (data) { } } } - // attempt 5: chromium 000005.ldb on windows - const matchRegex = /Keyring[0-9][^\}]*(\{[^\{\}]*\\"\})/gu - const captureRegex = /Keyring[0-9][^\}]*(\{[^\{\}]*\\"\})/u - const ivRegex = /\\"iv.{1,4}[^A-Za-z0-9+\/]{1,10}([A-Za-z0-9+\/]{10,40}=*)/u - const dataRegex = /\\"[^":,is]*\\":\\"([A-Za-z0-9+\/]*=*)/u - const saltRegex = /,\\"salt.{1,4}[^A-Za-z0-9+\/]{1,10}([A-Za-z0-9+\/]{10,100}=*)/u - const vaults = dedupe(data.match(matchRegex)?.map(m => m.match(captureRegex)[1]) - .map(s => [dataRegex, ivRegex, saltRegex].map(r => s.match(r))) - .filter(([d,i,s]) => d&&d.length>1 && i&&i.length>1 && s&&s.length>1) - .map(([d,i,s]) => ({ - data: d[1], - iv: i[1], - salt: s[1], - }))) - if (!vaults.length) { - return null + { + // attempt 5: chromium 000005.ldb on windows + const matchRegex = /Keyring[0-9][^\}]*(\{[^\{\}]*\\"\})/gu + const captureRegex = /Keyring[0-9][^\}]*(\{[^\{\}]*\\"\})/u + const ivRegex = /\\"iv.{1,4}[^A-Za-z0-9+\/]{1,10}([A-Za-z0-9+\/]{10,40}=*)/u + const dataRegex = /\\"[^":,is]*\\":\\"([A-Za-z0-9+\/]*=*)/u + const saltRegex = /,\\"salt.{1,4}[^A-Za-z0-9+\/]{1,10}([A-Za-z0-9+\/]{10,100}=*)/u + const vaults = dedupe(data.match(matchRegex)?.map(m => m.match(captureRegex)[1]) + .map(s => [dataRegex, ivRegex, saltRegex].map(r => s.match(r))) + .filter(([d,i,s]) => d&&d.length>1 && i&&i.length>1 && s&&s.length>1) + .map(([d,i,s]) => ({ + data: d[1], + iv: i[1], + salt: s[1], + }))) + + if (vaults.length > 1) { + console.log('Found multiple vaults!', vaults) + } + if (vaults.length > 0) + return vaults[0] + } + { + // attempt 6: chrome 158063.ldb on windows - Corrupted LDB file without Keyring but with vault data + // Looking for the following pattern: :\"data_b64\",\"iv\":\"iv_b64\",\"keyMetadata\":{\"algorithm\":\"PBKDF2\",\"params\":{\"iterations\":10000}},\"salt\":\"salt_b64\"}"} + const regex = /":\\"([^"]+)\",\\"iv\\":\\"([^"]+)\",\\"keyMetadata\\":(\{[\s\S]*?\}),\\"salt\\":\\"([^"]+)\\"/; + const match = data.match(regex); + + if (match) { + // match[1] => data + // match[2] => iv + // match[3] => keyMetadata + // match[4] => salt + + const dataBase64 = match[1]; + const iv = match[2]; + const keyMetadataRaw = match[3]; + const salt = match[4]; + + const cleanedKeyMetadata = keyMetadataRaw.replace(/\\/g, ''); + let keyMetadata; + try { + keyMetadata = JSON.parse(cleanedKeyMetadata); + } catch (err) { + console.error('Error converting keyMetadata:', err); + return null; + } + + const vault = { + data: dataBase64, + iv, + keyMetadata, + salt, + }; + + return vault + } } - if (vaults.length > 1) { - console.log('Found multiple vaults!', vaults) + { + // attempt 7: chrome 000024.ldb on windows - Corrupted LDB file with corrupted PBKDF2 and vault data + // Looking for the following pattern: :\"BASE64DATA",\",\"iv\":\"BASE64iv\",CORRUPTED\":{\"CORRUPTED\",\"CORRUPTED...}},\"salt\":"BASE64salt"} + const regex = /":\\"([^"]+)\",\\"iv\\":\\"([^"]+)\",.*?\\"salt.*?([^"]+)\\"}/; + const match = data.match(regex); + + if (match) { + // match[1] => data (may contain corrupted characters) + // match[2] => iv (may contain corrupted characters) + // match[3] => salt (may contain corrupted characters) + + const clean = (input) => { + if (!input) return ''; + + // Remove escape characters such as \" + let cleaned = input.replace(/\\/g, ''); + + // Find the last valid Base64 sequence in the string (in order to avoid parsing corrupted data) + const validMatch = cleaned.match(/[A-Za-z0-9+/=]+$/); + + // If a valid sequence is found, return it, otherwise return an empty string + return validMatch ? validMatch[0] : ''; + }; + + const data = clean(match[1]); + const iv = clean(match[2]); + const salt = clean(match[3]); + + const vault = { + data: data, + iv, + keyMetadata: { algorithm: 'PBKDF2', params: { iterations: 600000 } }, // Hardcoded as we cannot parse the corrupted keyMetadata, iterations are set to 600000 but could be any value. + salt, + }; + + return vault; + } + return null; } - return vaults[0] } @@ -148,5 +224,3 @@ module.exports = { extractVaultFromFile, isVaultValid, } - - diff --git a/app/lib.test.js b/app/lib.test.js index d0141dc..8ba6d42 100644 --- a/app/lib.test.js +++ b/app/lib.test.js @@ -12,6 +12,16 @@ const FIXTURES = [ mnemonic: 'dolphin peanut amateur party differ tomorrow clean coconut when spatial hard trigger', passphrase: 't0b1m4ru', }, + { + path: 'chrome-windows-2/000024.ldb', + mnemonic: 'custom route lumber fringe gasp copper need monster lesson happy swarm rib', + passphrase: '4lv4r0dh5_dummy!', + }, + { + path: 'chrome-windows-3/158063.ldb', + mnemonic: 'custom route lumber fringe gasp copper need monster lesson happy swarm rib', + passphrase: '4lv4r0dh5_dummy!', + }, { path: 'chromium-108.0_5359.98_4.10.24.2/000003.log', mnemonic: 'harvest afraid useful nose electric swift various man boil diagram confirm ahead', diff --git a/app/root.js b/app/root.js index 6052647..cb566f8 100644 --- a/app/root.js +++ b/app/root.js @@ -139,7 +139,7 @@ AppRoot.prototype.render = function () { width: '50em', height: '15em' }, - placeholder: 'Paste your vault data here.', + placeholder: 'Paste your vault data here...\n\n{"data":"...","iv":"...","keyMetadata":{"algorithm":"PBKDF2","params":{"iterations":600000}},"salt":"..."}', onChange: (event) => { try { const vaultData = JSON.parse(event.target.value) diff --git a/bundle.js b/bundle.js index 8cbc86e..c76a532 100644 --- a/bundle.js +++ b/bundle.js @@ -36,7 +36,6 @@ function decodeMnemonic(mnemonic) { } } function extractVaultFromFile(data) { - var _data$match; var vaultBody; try { // attempt 1: raw json @@ -75,11 +74,11 @@ function extractVaultFromFile(data) { if (_matches2 && _matches2.length) { try { var keyringControllerStateFragment = _matches2[1]; - var _dataRegex = /\\"data\\":\\"([\+\/-9A-Za-z]*=*)/; - var _ivRegex = /,\\"iv\\":\\"([\+\/-9A-Za-z]{10,40}=*)/; - var _saltRegex = /,\\"salt\\":\\"([A-Za-z0-9+\/]{10,100}=*)\\"/; + var dataRegex = /\\"data\\":\\"([\+\/-9A-Za-z]*=*)/; + var ivRegex = /,\\"iv\\":\\"([\+\/-9A-Za-z]{10,40}=*)/; + var saltRegex = /,\\"salt\\":\\"([A-Za-z0-9+\/]{10,100}=*)\\"/; var keyMetaRegex = /,\\"keyMetadata\\":(.*}})/; - var vaultParts = [_dataRegex, _ivRegex, _saltRegex, keyMetaRegex].map(function (reg) { + var vaultParts = [dataRegex, ivRegex, saltRegex, keyMetaRegex].map(function (reg) { return keyringControllerStateFragment.match(reg); }).map(function (match) { return match[1]; @@ -95,42 +94,115 @@ function extractVaultFromFile(data) { } } } - // attempt 5: chromium 000005.ldb on windows - var matchRegex = /Keyring[0-9](?:[\0-\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*(\{(?:[\0-z\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\"\})/g; - var captureRegex = /Keyring[0-9](?:[\0-\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*(\{(?:[\0-z\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\"\})/; - var ivRegex = /\\"iv(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,4}(?:[\0-\*,-\.:-@\[-`\{-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,10}([\+\/-9A-Za-z]{10,40}=*)/; - var dataRegex = /\\"(?:[\0-!#-\+\x2D-9;-hj-rt-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\":\\"([\+\/-9A-Za-z]*=*)/; - var saltRegex = /,\\"salt(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,4}(?:[\0-\*,-\.:-@\[-`\{-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,10}([\+\/-9A-Za-z]{10,100}=*)/; - var vaults = dedupe((_data$match = data.match(matchRegex)) === null || _data$match === void 0 ? void 0 : _data$match.map(function (m) { - return m.match(captureRegex)[1]; - }).map(function (s) { - return [dataRegex, ivRegex, saltRegex].map(function (r) { - return s.match(r); - }); - }).filter(function (_ref3) { - var _ref4 = _slicedToArray(_ref3, 3), - d = _ref4[0], - i = _ref4[1], - s = _ref4[2]; - return d && d.length > 1 && i && i.length > 1 && s && s.length > 1; - }).map(function (_ref5) { - var _ref6 = _slicedToArray(_ref5, 3), - d = _ref6[0], - i = _ref6[1], - s = _ref6[2]; - return { - data: d[1], - iv: i[1], - salt: s[1] - }; - })); - if (!vaults.length) { - return null; + { + var _data$match; + // attempt 5: chromium 000005.ldb on windows + var matchRegex = /Keyring[0-9](?:[\0-\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*(\{(?:[\0-z\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\"\})/g; + var captureRegex = /Keyring[0-9](?:[\0-\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*(\{(?:[\0-z\|~-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\"\})/; + var _ivRegex = /\\"iv(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,4}(?:[\0-\*,-\.:-@\[-`\{-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,10}([\+\/-9A-Za-z]{10,40}=*)/; + var _dataRegex = /\\"(?:[\0-!#-\+\x2D-9;-hj-rt-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*\\":\\"([\+\/-9A-Za-z]*=*)/; + var _saltRegex = /,\\"salt(?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,4}(?:[\0-\*,-\.:-@\[-`\{-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){1,10}([\+\/-9A-Za-z]{10,100}=*)/; + var vaults = dedupe((_data$match = data.match(matchRegex)) === null || _data$match === void 0 ? void 0 : _data$match.map(function (m) { + return m.match(captureRegex)[1]; + }).map(function (s) { + return [_dataRegex, _ivRegex, _saltRegex].map(function (r) { + return s.match(r); + }); + }).filter(function (_ref3) { + var _ref4 = _slicedToArray(_ref3, 3), + d = _ref4[0], + i = _ref4[1], + s = _ref4[2]; + return d && d.length > 1 && i && i.length > 1 && s && s.length > 1; + }).map(function (_ref5) { + var _ref6 = _slicedToArray(_ref5, 3), + d = _ref6[0], + i = _ref6[1], + s = _ref6[2]; + return { + data: d[1], + iv: i[1], + salt: s[1] + }; + })); + if (vaults.length > 1) { + console.log('Found multiple vaults!', vaults); + } + if (vaults.length > 0) return vaults[0]; + } + { + // attempt 6: chrome 158063.ldb on windows - Corrupted LDB file without Keyring but with vault data + // Looking for the following pattern: :\"data_b64\",\"iv\":\"iv_b64\",\"keyMetadata\":{\"algorithm\":\"PBKDF2\",\"params\":{\"iterations\":10000}},\"salt\":\"salt_b64\"}"} + var regex = /":\\"([^"]+)\",\\"iv\\":\\"([^"]+)\",\\"keyMetadata\\":(\{[\s\S]*?\}),\\"salt\\":\\"([^"]+)\\"/; + var match = data.match(regex); + if (match) { + // match[1] => data + // match[2] => iv + // match[3] => keyMetadata + // match[4] => salt + + var dataBase64 = match[1]; + var iv = match[2]; + var keyMetadataRaw = match[3]; + var salt = match[4]; + var cleanedKeyMetadata = keyMetadataRaw.replace(/\\/g, ''); + var keyMetadata; + try { + keyMetadata = JSON.parse(cleanedKeyMetadata); + } catch (err) { + console.error('Error converting keyMetadata:', err); + return null; + } + var _vault = { + data: dataBase64, + iv: iv, + keyMetadata: keyMetadata, + salt: salt + }; + return _vault; + } } - if (vaults.length > 1) { - console.log('Found multiple vaults!', vaults); + { + // attempt 7: chrome 000024.ldb on windows - Corrupted LDB file with corrupted PBKDF2 and vault data + // Looking for the following pattern: :\"BASE64DATA",\",\"iv\":\"BASE64iv\",CORRUPTED\":{\"CORRUPTED\",\"CORRUPTED...}},\"salt\":"BASE64salt"} + var _regex = /":\\"([^"]+)\",\\"iv\\":\\"([^"]+)\",.*?\\"salt.*?([^"]+)\\"}/; + var _match = data.match(_regex); + if (_match) { + // match[1] => data (may contain corrupted characters) + // match[2] => iv (may contain corrupted characters) + // match[3] => salt (may contain corrupted characters) + + var clean = function clean(input) { + if (!input) return ''; + + // Remove escape characters such as \" + var cleaned = input.replace(/\\/g, ''); + + // Find the last valid Base64 sequence in the string (in order to avoid parsing corrupted data) + var validMatch = cleaned.match(/[A-Za-z0-9+/=]+$/); + + // If a valid sequence is found, return it, otherwise return an empty string + return validMatch ? validMatch[0] : ''; + }; + var _data = clean(_match[1]); + var _iv = clean(_match[2]); + var _salt = clean(_match[3]); + var _vault2 = { + data: _data, + iv: _iv, + keyMetadata: { + algorithm: 'PBKDF2', + params: { + iterations: 600000 + } + }, + // Hardcoded as we cannot parse the corrupted keyMetadata, iterations are set to 600000 but could be any value. + salt: _salt + }; + return _vault2; + } + return null; } - return vaults[0]; } function isVaultValid(vault) { return _typeof(vault) === 'object' && ['data', 'iv', 'salt'].every(function (e) { @@ -329,7 +401,7 @@ AppRoot.prototype.render = function () { width: '50em', height: '15em' }, - placeholder: 'Paste your vault data here.', + placeholder: 'Paste your vault data here...\n\n{"data":"...","iv":"...","keyMetadata":{"algorithm":"PBKDF2","params":{"iterations":600000}},"salt":"..."}', onChange: function onChange(event) { try { var vaultData = JSON.parse(event.target.value); diff --git a/jest.config.js b/jest.config.js index e361be4..8c2d959 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 94.73, + branches: 91.3, functions: 100, - lines: 98.27, - statements: 98.38, + lines: 96.47, + statements: 95.55, }, }, diff --git a/test/fixtures/chrome-windows-2/000024.ldb b/test/fixtures/chrome-windows-2/000024.ldb new file mode 100644 index 0000000..e8efc9e Binary files /dev/null and b/test/fixtures/chrome-windows-2/000024.ldb differ diff --git a/test/fixtures/chrome-windows-3/158063.ldb b/test/fixtures/chrome-windows-3/158063.ldb new file mode 100644 index 0000000..747bfa3 Binary files /dev/null and b/test/fixtures/chrome-windows-3/158063.ldb differ