Skip to content
This repository has been archived by the owner on Apr 15, 2019. It is now read-only.

Commit

Permalink
Merge pull request #573 from LiskHQ/561-migrate_passphrase_command
Browse files Browse the repository at this point in the history
Migrate passphrase command - Closes #561
  • Loading branch information
shuse2 authored Aug 17, 2018
2 parents 034fa03 + 155aacb commit 11b8841
Show file tree
Hide file tree
Showing 6 changed files with 524 additions and 1 deletion.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@
"help": {
"description": "Displays help."
},
"passphrase": {
"description": "Commands relating to Lisk passphrases."
},
"warranty": {
"description": "Displays warranty notice."
}
Expand Down
91 changes: 91 additions & 0 deletions src/commands/passphrase/decrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* LiskHQ/lisk-commander
* Copyright © 2017–2018 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*
*/
import { flags as flagParser } from '@oclif/command';
import { cryptography } from 'lisk-elements';
import BaseCommand from '../../base';
import { ValidationError } from '../../utils/error';
import commonFlags from '../../utils/flags';
import getInputsFromSources, {
getFirstLineFromString,
} from '../../utils/input';

const passphraseOptionDescription = `Specifies a source for providing an encrypted passphrase to the command. If a string is provided directly as an argument, this option will be ignored. The encrypted passphrase must be provided via an argument or via this option. Sources must be one of \`file\` or \`stdin\`. In the case of \`file\`, a corresponding identifier must also be provided.
Note: if both an encrypted passphrase and the password are passed via stdin, the password must be the first line.
Examples:
- --passphrase file:/path/to/my/encrypted_passphrase.txt (takes the first line only)
- --passphrase stdin (takes the first line only)
`;

const processInputs = encryptedPassphrase => ({ password, data }) => {
const encryptedPassphraseObject = cryptography.parseEncryptedPassphrase(
encryptedPassphrase || getFirstLineFromString(data),
);
const passphrase = cryptography.decryptPassphraseWithPassword(
encryptedPassphraseObject,
password,
);
return { passphrase };
};

export default class DecryptCommand extends BaseCommand {
async run() {
const {
args: { encryptedPassphrase },
flags: { passphrase: passphraseSource, password: passwordSource },
} = this.parse(DecryptCommand);

if (!encryptedPassphrase && !passphraseSource) {
throw new ValidationError('No encrypted passphrase was provided.');
}
const inputs = await getInputsFromSources({
password: {
source: passwordSource,
},
data: encryptedPassphrase
? null
: {
source: passphraseSource,
},
});
const result = processInputs(encryptedPassphrase)(inputs);
this.print(result);
}
}

DecryptCommand.args = [
{
name: 'encryptedPassphrase',
description: 'Encrypted passphrase to decrypt.',
},
];

DecryptCommand.flags = {
...BaseCommand.flags,
password: flagParser.string(commonFlags.password),
passphrase: flagParser.string({
description: passphraseOptionDescription,
}),
};

DecryptCommand.description = `
Decrypts your secret passphrase using the password which was provided at the time of encryption.
`;

DecryptCommand.examples = [
'passphrase:decrypt "iterations=1000000&cipherText=9b1c60&iv=5c8843f52ed3c0f2aa0086b0&salt=2240b7f1aa9c899894e528cf5b600e9c&tag=23c01112134317a63bcf3d41ea74e83b&version=1"',
];
78 changes: 78 additions & 0 deletions src/commands/passphrase/encrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* LiskHQ/lisk-commander
* Copyright © 2017–2018 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*
*/
import { flags as flagParser } from '@oclif/command';
import { cryptography } from 'lisk-elements';
import BaseCommand from '../../base';
import commonFlags from '../../utils/flags';
import getInputsFromSources from '../../utils/input';

const outputPublicKeyOptionDescription =
'Includes the public key in the output. This option is provided for the convenience of node operators.';

const processInputs = outputPublicKey => ({ passphrase, password }) => {
const encryptedPassphraseObject = cryptography.encryptPassphraseWithPassword(
passphrase,
password,
);
const encryptedPassphrase = cryptography.stringifyEncryptedPassphrase(
encryptedPassphraseObject,
);
return outputPublicKey
? {
encryptedPassphrase,
publicKey: cryptography.getKeys(passphrase).publicKey,
}
: { encryptedPassphrase };
};

export default class EncryptCommand extends BaseCommand {
async run() {
const {
flags: {
passphrase: passphraseSource,
password: passwordSource,
outputPublicKey,
},
} = this.parse(EncryptCommand);
const inputs = await getInputsFromSources({
passphrase: {
source: passphraseSource,
repeatPrompt: true,
},
password: {
source: passwordSource,
repeatPrompt: true,
},
});
const result = processInputs(outputPublicKey)(inputs);
this.print(result);
}
}

EncryptCommand.flags = {
...BaseCommand.flags,
password: flagParser.string(commonFlags.password),
passphrase: flagParser.string(commonFlags.passphrase),
outputPublicKey: flagParser.boolean({
description: outputPublicKeyOptionDescription,
}),
};

EncryptCommand.description = `
Encrypts your secret passphrase under a password.
`;

EncryptCommand.examples = ['passphrase:encrypt'];
2 changes: 1 addition & 1 deletion src/utils/flags.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const flags = {
description: secondPassphraseDescription,
},
password: {
char: '2',
char: 'w',
description: passwordDescription,
},
unvotes: {
Expand Down
155 changes: 155 additions & 0 deletions test/commands/passphrase/decrypt.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* LiskHQ/lisk-commander
* Copyright © 2017–2018 Lisk Foundation
*
* See the LICENSE file at the top-level directory of this distribution
* for licensing information.
*
* Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation,
* no part of this software, including this file, may be copied, modified,
* propagated, or distributed except according to the terms contained in the
* LICENSE file.
*
* Removal or modification of this copyright notice is prohibited.
*
*/
import { expect, test } from '@oclif/test';
import { cryptography } from 'lisk-elements';
import * as config from '../../../src/utils/config';
import * as print from '../../../src/utils/print';
import * as getInputsFromSources from '../../../src/utils/input';

describe('passphrase:decrypt', () => {
const defaultEncryptedPassphrase =
'salt=d3887df959ed2bfe5961a6831da6e177&cipherText=1c08a1&iv=096ede534df9092fd4523ec7&tag=2a055e1c860b3ef76084a6c9aca68ce9&version=1';
const passphrase =
'enemy pill squeeze gold spoil aisle awake thumb congress false box wagon';
const encryptedPassphraseObject = {
salt: 'salt',
cipherText: 'cipherText',
iv: 'iv',
tag: 'tag',
version: 1,
};
const defaultInputs = {
password: 'LbYpLpV9Wpec6ux8',
data: `${defaultEncryptedPassphrase}\nshould not be used`,
};

const printMethodStub = sandbox.stub();
const setupTest = () =>
test
.stub(print, 'default', sandbox.stub().returns(printMethodStub))
.stub(config, 'getConfig', sandbox.stub().returns({}))
.stub(
cryptography,
'parseEncryptedPassphrase',
sandbox.stub().returns(encryptedPassphraseObject),
)
.stub(
cryptography,
'decryptPassphraseWithPassword',
sandbox.stub().returns(passphrase),
)
.stub(
getInputsFromSources,
'default',
sandbox.stub().resolves(defaultInputs),
)
.stdout();

describe('passphrase:decrypt', () => {
setupTest()
.command(['passphrase:decrypt'])
.catch(error => {
return expect(error.message).to.contain(
'No encrypted passphrase was provided.',
);
})
.it('should throw an error');
});

describe('passphrase:decrypt encryptedPassphrase', () => {
setupTest()
.command(['passphrase:decrypt', defaultEncryptedPassphrase])
.it('should decrypt passphrase with arg', () => {
expect(getInputsFromSources.default).to.be.calledWithExactly({
password: {
source: undefined,
},
data: null,
});
expect(cryptography.parseEncryptedPassphrase).to.be.calledWithExactly(
defaultEncryptedPassphrase,
);
expect(
cryptography.decryptPassphraseWithPassword,
).to.be.calledWithExactly(
encryptedPassphraseObject,
defaultInputs.password,
);
return expect(printMethodStub).to.be.calledWithExactly({ passphrase });
});
});

describe('passphrase:decrypt --passphrase=file:./path/to/encrypted_passphrase.txt', () => {
const passphraseSource = 'file:./path/to/encrypted_passphrase.txt';
setupTest()
.command(['passphrase:decrypt', `--passphrase=${passphraseSource}`])
.it('should decrypt passphrase with passphrase flag', () => {
expect(getInputsFromSources.default).to.be.calledWithExactly({
password: {
source: undefined,
},
data: {
source: passphraseSource,
},
});
expect(cryptography.parseEncryptedPassphrase).to.be.calledWithExactly(
defaultEncryptedPassphrase,
);
expect(
cryptography.decryptPassphraseWithPassword,
).to.be.calledWithExactly(
encryptedPassphraseObject,
defaultInputs.password,
);
return expect(printMethodStub).to.be.calledWithExactly({ passphrase });
});
});

describe('passphrase:decrypt --passphrase=filePath --password=pass:LbYpLpV9Wpec6ux8', () => {
const passphraseSource = 'file:./path/to/encrypted_passphrase.txt';
setupTest()
.command([
'passphrase:decrypt',
`--passphrase=${passphraseSource}`,
'--password=pass:LbYpLpV9Wpec6ux8',
])
.it(
'should decrypt passphrase with passphrase flag and password flag',
() => {
expect(getInputsFromSources.default).to.be.calledWithExactly({
password: {
source: 'pass:LbYpLpV9Wpec6ux8',
},
data: {
source: passphraseSource,
},
});
expect(cryptography.parseEncryptedPassphrase).to.be.calledWithExactly(
defaultEncryptedPassphrase,
);
expect(
cryptography.decryptPassphraseWithPassword,
).to.be.calledWithExactly(
encryptedPassphraseObject,
defaultInputs.password,
);
return expect(printMethodStub).to.be.calledWithExactly({
passphrase,
});
},
);
});
});
Loading

0 comments on commit 11b8841

Please sign in to comment.