Skip to content

Commit

Permalink
Merge pull request #21 from VirgilSecurity/resetPrivateKeyBackup-with…
Browse files Browse the repository at this point in the history
…out-password

Reset private key backup without password
  • Loading branch information
Alexey Smirnov authored Feb 22, 2019
2 parents 0b1137b + 2264b0c commit c282be0
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 115 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 2019-02-21 0.3.7

* Changed method signature of EThree.resetPrivateKeyBackup(pwd?). Now password is optional. But if you using the Virgil Keyknox service directly to save some additional data, you should use password to not delete this data.

## 2019-02-13 0.3.6

* Fixed compatibility with JWT generated by Python SDK

## 2019-01-10 0.3.5

* Added React Native support, see https://github.com/VirgilSecurity/virgil-e3kit-js#react-native-usage

## 2018-12-22 0.3.4

* Fixed broken buildings bugs
Expand All @@ -12,6 +24,7 @@
* New method EThree.rotatePrivateKey() - used in case you lost your private key
* New method EThree.restorePrivateKey(pwd) - used to fetch private key from Virgil Cloud
* New method EThree.hasLocalPrivateKey() - used to check for private key existence on a device
* New method EThree.resetPrivateKeyBackup(pwd) - used to reset private key backup.

## 2018-11-20 0.2.0

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ You will need to add `@virgilsecurity/e3kit` script.

### Initialize & Register

```
```js
import { EThree } from '@virgilsecurity/e3kit-js'
// get virgil token from you backend (better to protect it!)
const getToken = () => fetch('http://localhost:3000/get-virgil-jwt/')
Expand All @@ -50,7 +50,7 @@ await sdk.backupPrivateKey('encryption_pwd');

### Encrypt & Decrypt

```
```js
const usersToEncryptTo = ["alice@myapp.com", "bob@myapp.com", 'sofia@myapp.com'];
const userThatEncrypts = "alex@myapp.com";
const [receiverPublicKeys, senderPublicKey] = await Promise.all([
Expand All @@ -70,7 +70,7 @@ You can find more examples in [examples folder](example) and on https://e3kit.re

This package works with https://github.com/VirgilSecurity/virgil-key-storage-rn

```
```js
import { EThree } from '@virgilsecurity/e3kit';
import createNativeKeyEntryStorage from '@virgilsecurity/key-storage-rn/native';
// or
Expand Down
18 changes: 9 additions & 9 deletions config/rollup-plugins.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require('dotenv').config();
const paths = require('./paths');

const commonjs = require('rollup-plugin-commonjs');
Expand All @@ -8,11 +9,10 @@ const typescript = require('rollup-plugin-typescript2');
const { uglify } = require('rollup-plugin-uglify');
const sourcemap = require('rollup-plugin-sourcemaps');


function resolveVirgilCrypto () {
function resolveVirgilCrypto() {
return {
name: 'resolve-virgil-crypto',
resolveId (importee, b) {
resolveId(importee, b) {
if (importee === 'virgil-crypto') {
return paths.pythiaCryptoPath;
}
Expand All @@ -34,12 +34,12 @@ class RollupPluginsResolver {
useTsconfigDeclarationDir: true,
});
this.inject = inject({
include: '**/*.ts',
exclude: 'node_modules/**',
modules: {
Buffer: [ 'buffer-es6', 'Buffer' ]
}
})
include: '**/*.ts',
exclude: 'node_modules/**',
modules: {
Buffer: ['buffer-es6', 'Buffer']
}
});
}
}

Expand Down
2 changes: 1 addition & 1 deletion config/rollup.config.es.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ module.exports = {
plugins.commonjs(),
plugins.typescriptResolved,
plugins.nodeGlobals,
plugins.inject,
plugins.inject,
],
};
21 changes: 8 additions & 13 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
const replace = require('rollup-plugin-replace');
// const json = require('rollup-plugin-json');
const umdConfig = require('./config/rollup.config.umd');

require('dotenv').config();
const replace = require('rollup-plugin-replace');

module.exports = function (config) {
config.set({
Expand Down Expand Up @@ -30,15 +27,13 @@ module.exports = function (config) {
},

rollupPreprocessor: {
plugins: [
replace({
'process.env.API_KEY_ID': JSON.stringify(process.env.API_KEY_ID),
'process.env.API_KEY': JSON.stringify(process.env.API_KEY),
'process.env.APP_ID': JSON.stringify(process.env.APP_ID),
'process.env.NODE_ENV': JSON.stringify('production'),
}),
...umdConfig.plugins,
],
plugins: [...umdConfig.plugins, this.replace = replace({
'process.env.API_KEY_ID': JSON.stringify(process.env.API_KEY_ID),
'process.env.API_KEY': JSON.stringify(process.env.API_KEY),
'process.env.APP_ID': JSON.stringify(process.env.APP_ID),
'process.env.API_URL': JSON.stringify(process.env.API_URL),
'process.env.NODE_ENV': process.env.NODE_ENV || JSON.stringify('production'),
})],
output: umdConfig.output,
},
});
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@virgilsecurity/e3kit",
"version": "0.3.6",
"version": "0.3.7",
"description": "End-to-end encryption with multiple device support powered by Virgil Security",
"main": "./dist/e3kit.browser.es.js",
"module": "./dist/e3kit.browser.es.js",
Expand Down Expand Up @@ -30,7 +30,7 @@
]
},
"dependencies": {
"@virgilsecurity/keyknox": "^0.2.2",
"@virgilsecurity/keyknox": "^0.2.4",
"virgil-crypto": "^3.2.0",
"virgil-pythia": "^0.2.3",
"virgil-sdk": "^5.2.2"
Expand Down
27 changes: 20 additions & 7 deletions src/EThree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import {
} from './errors';
import { isArray, isString } from './utils/typeguards';
import { hasDuplicates, getObjectValues } from './utils/array';
import { withDefaults } from './utils/object';

interface IEThreeInitOptions {
keyEntryStorage?: IKeyEntryStorage;
apiUrl?: string;
}

interface IEThreeCtorOptions extends IEThreeInitOptions {
Expand All @@ -54,12 +56,13 @@ type EncryptVirgilPublicKeyArg = LookupResult | VirgilPublicKey;
const _inProcess = Symbol('inProcess');
const _keyLoader = Symbol('keyLoader');
const STORAGE_NAME = '.virgil-local-storage';

const DEFAULT_API_URL = 'https://api.virgilsecurity.com';
export default class EThree {
identity: string;
virgilCrypto = new VirgilCrypto();
cardCrypto = new VirgilCardCrypto(this.virgilCrypto);
cardVerifier = new VirgilCardVerifier(this.cardCrypto);
cardVerifier: VirgilCardVerifier;

cardManager: CardManager;
accessTokenProvider: IAccessTokenProvider;
keyEntryStorage: IKeyEntryStorage;
Expand All @@ -68,28 +71,37 @@ export default class EThree {
private [_inProcess]: boolean = false;

static async initialize(getToken: () => Promise<string>, options: IEThreeInitOptions = {}) {
const opts = { accessTokenProvider: new CachingJwtProvider(getToken), ...options };
const opts = withDefaults(options as IEThreeCtorOptions, {
accessTokenProvider: new CachingJwtProvider(getToken),
});
const token = await opts.accessTokenProvider.getToken({ operation: 'get' });
const identity = token.identity();
return new EThree(identity, opts);
}

constructor(identity: string, options: IEThreeCtorOptions) {
const opts = withDefaults(options, { apiUrl: DEFAULT_API_URL });
this.identity = identity;
this.accessTokenProvider = options.accessTokenProvider;
this.keyEntryStorage = options.keyEntryStorage || new KeyEntryStorage(STORAGE_NAME);
this.accessTokenProvider = opts.accessTokenProvider;
this.keyEntryStorage = opts.keyEntryStorage || new KeyEntryStorage(STORAGE_NAME);
this.cardVerifier = new VirgilCardVerifier(this.cardCrypto, {
verifySelfSignature: opts.apiUrl === DEFAULT_API_URL,
verifyVirgilSignature: opts.apiUrl === DEFAULT_API_URL,
});

this[_keyLoader] = new PrivateKeyLoader(this.identity, {
accessTokenProvider: this.accessTokenProvider,
virgilCrypto: this.virgilCrypto,
keyEntryStorage: this.keyEntryStorage,
apiUrl: opts.apiUrl,
});

this.cardManager = new CardManager({
cardCrypto: this.cardCrypto,
cardVerifier: this.cardVerifier,
accessTokenProvider: this.accessTokenProvider,
retryOnUnauthorized: true,
apiUrl: opts.apiUrl,
});
}

Expand Down Expand Up @@ -147,8 +159,9 @@ export default class EThree {
await this[_keyLoader].resetLocalPrivateKey();
}

async resetPrivateKeyBackup(password: string) {
return this[_keyLoader].resetBackupPrivateKey(password);
async resetPrivateKeyBackup(pwd?: string) {
if (!pwd) return await this[_keyLoader].resetAll();
return this[_keyLoader].resetPrivateKeyBackup(pwd);
}

async encrypt(
Expand Down
49 changes: 17 additions & 32 deletions src/PrivateKeyLoader.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
import { createBrainKey } from 'virgil-pythia';
import {
CloudKeyStorage,
KeyknoxManager,
KeyknoxCrypto,
CloudEntryDoesntExistError,
KeyknoxClient,
} from '@virgilsecurity/keyknox';
import {
VirgilPythiaCrypto,
VirgilPublicKey,
VirgilPrivateKey,
VirgilCrypto,
} from 'virgil-crypto/dist/virgil-crypto-pythia.es';
import { IKeyEntryStorage, IAccessTokenProvider } from 'virgil-sdk';
import { WrongKeyknoxPasswordError, PrivateKeyNoBackupError } from './errors';

const BRAIN_KEY_RATE_LIMIT_DELAY = 2000;
const BRAIN_KEY_THROTTLING_ERROR_CODE = 60007;

type KeyPair = {
privateKey: VirgilPrivateKey;
publicKey: VirgilPublicKey;
};
import { generateBrainPair } from './utils/brainkey';

export interface IPrivateKeyLoaderOptions {
virgilCrypto: VirgilCrypto;
accessTokenProvider: IAccessTokenProvider;
keyEntryStorage: IKeyEntryStorage;
apiUrl?: string;
}

export default class PrivateKeyLoader {
private pythiaCrypto = new VirgilPythiaCrypto();
private localStorage: IKeyEntryStorage;
private keyknoxClient = new KeyknoxClient(this.options.apiUrl);
private keyknoxCrypto = new KeyknoxCrypto(this.options.virgilCrypto);

constructor(private identity: string, public options: IPrivateKeyLoaderOptions) {
this.localStorage = options.keyEntryStorage;
Expand Down Expand Up @@ -61,14 +55,19 @@ export default class PrivateKeyLoader {
await this.localStorage.remove(this.identity).catch(this.handleResetError);
}

async resetBackupPrivateKey(password: string) {
async resetPrivateKeyBackup(password: string) {
const storage = await this.getStorage(password);
await storage.deleteEntry(this.identity).catch(this.handleResetError);
}

async resetAll() {
const token = await this.options.accessTokenProvider.getToken({ operation: 'delete' });
await this.keyknoxClient.resetValue(token.toString());
}

async restorePrivateKey(password: string) {
const storage = await this.getStorage(password);
const rawKey = await storage.retrieveEntry(this.identity);
const rawKey = storage.retrieveEntry(this.identity);
await this.localStorage.save({ name: this.identity, value: rawKey.data });
return this.options.virgilCrypto.importPrivateKey(rawKey.data);
}
Expand All @@ -95,25 +94,11 @@ export default class PrivateKeyLoader {
};

private async generateBrainPair(pwd: string) {
const brainKey = createBrainKey({
return generateBrainPair(pwd, {
virgilCrypto: this.options.virgilCrypto,
virgilPythiaCrypto: this.pythiaCrypto,
pythiaCrypto: this.pythiaCrypto,
accessTokenProvider: this.options.accessTokenProvider,
});

return await brainKey.generateKeyPair(pwd).catch((e: Error & { code?: number }) => {
if (typeof e === 'object' && e.code === BRAIN_KEY_THROTTLING_ERROR_CODE) {
const promise = new Promise((resolve, reject) => {
const repeat = () =>
brainKey
.generateKeyPair(pwd)
.then(resolve)
.catch(reject);
setTimeout(repeat, BRAIN_KEY_RATE_LIMIT_DELAY);
});
return promise as Promise<KeyPair>;
}
throw e;
apiUrl: this.options.apiUrl,
});
}

Expand All @@ -125,8 +110,8 @@ export default class PrivateKeyLoader {
this.options.accessTokenProvider,
keyPair.privateKey,
keyPair.publicKey,
undefined,
new KeyknoxCrypto(this.options.virgilCrypto),
this.keyknoxClient,
this.keyknoxCrypto,
),
);
try {
Expand Down
Loading

0 comments on commit c282be0

Please sign in to comment.