diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bb719fb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2021 Vault12, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index f0fc112..11ab35c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,47 @@ -## Description - -## How to use -1. Install [nodejs](https://nodejs.org/uk/) -2. Open terminal -3. Run `npm i -g @vault12/recovery-utility` -4. Export external vault on guardian devices `Settings > Advanced > Export Data for External Vault` -5. Export your vault master key `Settings > Advanced > Export My Vault’s Decryption Key` -6. Put all exported files to same directory `/directory/with/exported/files` -7. In terminal run `@vault12-recovery /directory/with/exported/files` -8. All files will be recovered in `/directory/with/exported/files/output` +

+ Vault12 Recovery Utility +

+ +

+ + MIT License + + Dependencies + + PRs welcome + + + Follow + +

+ +# Overwiew + +A command-line utility for recovering assets from the raw data exported from [Vault12 mobile app](https://vault12.com/download/). + +## Installation + +1. Install [node](https://nodejs.org/) +2. Install the package globally: +``` +$ npm i -g vault12-recovery +``` +## Usage + +1. Export the decryption key (`vault12.json`) from the Vault12 app: *Settings > Advanced > Export My Vault’s Decryption Key*. +2. Collect the raw Vault data from several Guardian devices via *Settings > Advanced > Export Data for External Vault*. You'll need to collect the amount of files equal to the **Number of Confirmations** you selected when creating the Vault. +3. Place all exported archives and `vault12.json` file in the same directory, e.g. `~/vault12-files`. +4. In terminal, run +``` +$ vault12-recovery ~/vault12-files +``` +5. You should find all recovered assets from the Vault in the directory `~/vault12-files/output`. + +## License + +RingCypher is released under the [MIT License](http://opensource.org/licenses/MIT). + +## Legal Reminder + +Exporting/importing and/or use of strong cryptography software, providing cryptography hooks, or even just communicating technical details about cryptography software is illegal in some parts of the world. If you import this software to your country, re-distribute it from there or even just email technical suggestions or provide source patches to the authors or other people you are strongly advised to pay close attention to any laws or regulations which apply to you. The authors of this software are not liable for any violations you make - it is your responsibility to be aware of and comply with any laws or regulations which apply to you. diff --git a/index.ts b/index.ts index edebf43..05141c5 100755 --- a/index.ts +++ b/index.ts @@ -5,7 +5,7 @@ import { ExportAssetMetadata, ExportIndexFileModel, ExportVaultMetadata } from ' const AdmZip = require('adm-zip'); const calcMd5 = require('md5'); const { join } = require('shamir'); -const sodium = require('sodium-javascript') +const sodium = require('sodium-javascript'); const pathLib = require('path'); const outputDir = 'output'; @@ -25,24 +25,24 @@ function getDirectory() { throw new Error('First parameter should be a directory'); } if (!fs.lstatSync(dir).isDirectory()) { - throw new Error(`${dir} expect to be a directory`); + throw new Error(`${dir} expected to be a directory`); } return dir; } function getVaultData(): ExportVaultMetadata { const keyFilePath = path(keyFile); - if(!fs.lstatSync(keyFilePath).isFile()) { - throw new Error(`Expect ${dir} to contain ${keyFilePath}`); + if (!fs.lstatSync(keyFilePath).isFile()) { + throw new Error(`Expected ${dir} to contain ${keyFilePath}`); } - return JSON.parse(fs.readFileSync(keyFilePath, {encoding: 'utf-8'})); + return JSON.parse(fs.readFileSync(keyFilePath, { encoding: 'utf-8' })); } function getZipArchives() { const zipArchives = fs.readdirSync(dir).filter(p => pathLib.extname(p) === '.zip'); if (zipArchives.length < vaultData.shardsRequiredToUnlock) { - throw new Error(`According to your security setting you need to receive data from ${vaultData.shardsRequiredToUnlock} guardians but have only ${zipArchives.length}`); + throw new Error(`According to your security policy, you need to receive data from ${vaultData.shardsRequiredToUnlock} guardians, but you provided only ${zipArchives.length}`); } return zipArchives; @@ -55,7 +55,7 @@ function restoreAssets() { try { recombinedFile = recombineAsset(asset) } catch (error) { - console.error(`Failed to recombine ${asset.name}`, error); + console.error(`Failed to recover ${asset.name}`, error); return; } let plainText: Buffer; @@ -66,13 +66,13 @@ function restoreAssets() { return; } fs.writeFileSync(path(outputDir, asset.name), plainText); - console.log(`${asset.name} successfully unlocked`) + console.log(`${asset.name} successfully unlocked`); }) console.log(`Assets successfully unlocked and stored in ${workingOutputDir}`); } function createDir(workingOutputDir: string) { - if (!fs.existsSync(workingOutputDir)){ + if (!fs.existsSync(workingOutputDir)) { fs.mkdirSync(workingOutputDir); } return workingOutputDir; @@ -84,11 +84,11 @@ function findAssetShardsInArchives() { new AdmZip(path(zipArchive)).extractAllTo(path(archiveDirName), true); - const {shards} = getArchiveIndex(archiveDirName); + const { shards } = getArchiveIndex(archiveDirName); vaultData.assetsMetaData.forEach(asset => { const foundShard = asset.shards.find((assetShard) => { - const foundShard = shards.find(({shardId}) => shardId === assetShard.id); + const foundShard = shards.find(({ shardId }) => shardId === assetShard.id); if (!foundShard) { return false; } @@ -105,10 +105,10 @@ function findAssetShardsInArchives() { } function getArchiveIndex(archiveDirName: string) { - return JSON.parse(fs.readFileSync(path(archiveDirName, indexFile), {encoding: 'utf-8'})) as ExportIndexFileModel + return JSON.parse(fs.readFileSync(path(archiveDirName, indexFile), { encoding: 'utf-8' })) as ExportIndexFileModel; } -function validateFile(filePath: string, expectedMd5: string) { +function validateFile(filePath: string, expectedMd5: string) { const fileData = fs.readFileSync(filePath); const md5 = calcMd5(fileData); if (md5 !== expectedMd5) { @@ -121,28 +121,28 @@ function path(...args: string[]) { } function recombineAsset(asset: ExportAssetMetadata) { - const shardPaths = asset.shards.filter(shard => !!shard.path).map(shard => shard.path); + const shardPaths = asset.shards.filter(shard => !!shard.path).map(shard => shard.path); - if (shardPaths.length < vaultData.shardsRequiredToUnlock) { - console.error(`Only ${shardPaths.length} shards for file ${asset.name} when ${vaultData.shardsRequiredToUnlock} needed`); - return; - } - /** - * prepare for format required by shamir - * 1st byte of file is index of shard the rest is data - */ - const buffers = shardPaths.map((path) => fs.readFileSync(path)); - const obj = {}; - buffers.forEach((v) => obj[v[0]]=v.slice(1)); - return join(obj); + if (shardPaths.length < vaultData.shardsRequiredToUnlock) { + console.error(`Only ${shardPaths.length} shards found for asset ${asset.name} when ${vaultData.shardsRequiredToUnlock} needed`); + return; + } + /** + * prepare for format required by shamir + * 1st byte of file is index of shard the rest is data + */ + const buffers = shardPaths.map((path) => fs.readFileSync(path)); + const obj = {}; + buffers.forEach((v) => obj[v[0]] = v.slice(1)); + return join(obj); } function decryptAsset(asset: ExportAssetMetadata, encryptedData: Buffer, masterKey: Buffer) { - const nonce = Buffer.from(asset.nonce, 'base64'); + const nonce = Buffer.from(asset.nonce, 'base64'); const plainText = Buffer.alloc(encryptedData.length - sodium.crypto_secretbox_MACBYTES); const res = sodium.crypto_secretbox_open_easy(plainText, encryptedData, nonce, masterKey); if (!res) { - throw new Error(`Failed to encrypt ${asset.name}`); + throw new Error(`Failed to decrypt ${asset.name}`); } return plainText; -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index f7a1fa4..526828e 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { - "name": "@vault12/recovery-utility", - "version": "0.1.0", + "name": "vault12-recovery", + "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "@vault12/recovery-utility", - "version": "0.1.0", - "license": "ISC", + "name": "vault12-recovery", + "version": "1.0.1", + "license": "MIT", "dependencies": { "adm-zip": "0.5.2", "md5": "2.3.0", @@ -20,10 +20,10 @@ "devDependencies": { "@types/adm-zip": "0.4.33", "@types/node": "14.14.25", - "typescript": "4.1.4" + "typescript": "4.2.2" }, "engines": { - "node": "15.8.0" + "node": ">=14" } }, "node_modules/@types/adm-zip": { @@ -185,9 +185,9 @@ } }, "node_modules/typescript": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.4.tgz", - "integrity": "sha512-+Uru0t8qIRgjuCpiSPpfGuhHecMllk5Zsazj5LZvVsEStEjmIRRBZe+jHjGQvsgS7M1wONy2PQXd67EMyV6acg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -360,9 +360,9 @@ } }, "typescript": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.4.tgz", - "integrity": "sha512-+Uru0t8qIRgjuCpiSPpfGuhHecMllk5Zsazj5LZvVsEStEjmIRRBZe+jHjGQvsgS7M1wONy2PQXd67EMyV6acg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.2.tgz", + "integrity": "sha512-tbb+NVrLfnsJy3M59lsDgrzWIflR4d4TIUjz+heUnHZwdF7YsrMTKoRERiIvI2lvBG95dfpLxB21WZhys1bgaQ==", "dev": true }, "xsalsa20": { diff --git a/package.json b/package.json index 28d202c..df30ec1 100755 --- a/package.json +++ b/package.json @@ -1,13 +1,24 @@ { - "name": "@vault12/recovery-utility", - "version": "0.1.0", - "description": "desktop utility for unlocking assets for vault12 app", + "name": "vault12-recovery", + "version": "1.0.1", + "description": "A command-line utility for recovering assets from the raw data exported from Vault12 mobile app", "main": "index.js", "author": "Vault12", "homepage": "https://vault12.com", - "license": "ISC", + "repository": { + "type": "git", + "url": "https://github.com/vault12/recovery-utility.git" + }, + "keywords": [ + "vault12", + "unlock", + "recovery", + "vault", + "utility" + ], + "license": "MIT", "engines": { - "node": "15.8.0" + "node": ">=14" }, "bin": { "vault12-recovery": "./index.js" @@ -21,7 +32,7 @@ "devDependencies": { "@types/adm-zip": "0.4.33", "@types/node": "14.14.25", - "typescript": "4.1.4" + "typescript": "4.2.2" }, "dependencies": { "adm-zip": "0.5.2",