Skip to content

Commit

Permalink
Merge pull request #2 from vault12/prepare-release
Browse files Browse the repository at this point in the history
Prepare release
  • Loading branch information
pavlo-liapin authored Feb 25, 2021
2 parents 66f08dd + 896c296 commit 8a50123
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 59 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -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.
58 changes: 47 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -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`
<p align="center">
<img src="https://user-images.githubusercontent.com/1370944/109153827-15c4d700-7776-11eb-93c0-6801f8d618b0.jpg"
alt="Vault12 Recovery Utility">
</p>

<p align="center">
<a href="https://opensource.org/licenses/MIT">
<img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="MIT License" />
</a>
<img src="https://img.shields.io/david/vault12/recovery-utility" alt="Dependencies" />
<a href="http://makeapullrequest.com">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg" alt="PRs welcome" />
</a>
<a href="https://twitter.com/_Vault12_">
<img src="https://img.shields.io/twitter/follow/_Vault12_?label=Follow&style=social" alt="Follow" />
</a>
</p>

# 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.
58 changes: 29 additions & 29 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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) {
Expand All @@ -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;
}
}
26 changes: 13 additions & 13 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 17 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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",
Expand Down

0 comments on commit 8a50123

Please sign in to comment.