UNPKG

@ubiquits/toolchain

Version:

Toolchain for ubiquits projects, modules & core

238 lines (187 loc) 6.44 kB
const {pki} = require('node-forge'); const {rsa} = pki; const fs = require('fs-extra'); const jwt = require('jsonwebtoken'); const inquirer = require('inquirer'); function task(cli, project) { const owners = ['admin', 'server']; cli.command('key generate <owner>', 'Generate RSA keys') .autocomplete(owners) .validate((args) => { if (owners.includes(args.owner)) { return true; } return `<owner> must be one of [${owners.join(', ')}]. '${args.owner}' given`; }) .option('-p, --password [password]', 'Password to encrypt the private key') .action(function (args, callback) { return generateKeyPair(this, args.options.password, args.owner) .then((keys) => saveKeys(this, project, keys, args.owner)); }); cli.command('jwt <owner>', 'Generate JSON Web Token signed by <owner>') .autocomplete(owners) .action(function (args, callback) { return getSignedJwt(this, project, args.owner) .then(({jwt}) => { this.log('JWT output follows'); this.log(jwt); }); }); } function getKeyPaths(project, owner = 'server') { let destination = ['/keys', '/keys']; let names = ['private.key', 'public.pub']; switch (owner) { case 'admin': destination = ['/_private', '/keys/known_hosts']; names = [`${process.env.USER}-private.key`, `${process.env.USER}-public.pub`]; break; } return { private: project.basePath + destination[0] + '/' + names[0], public: project.basePath + destination[1] + '/' + names[1], }; } function generateKeyPair(cli, password = false, owner = 'server') { if (!!password && owner == 'server') { this.log('Keys cannot be encrypted for the server key-pair'); return Promise.reject(); } return Promise.resolve(password) .then((password) => { if (!!password || owner == 'server') { return password; } let repeatAttempts = 0; return inquirer.prompt([ { name: 'usePassword', type: 'confirm', message: 'Do you want to password encrypt your private key?\nYou will need to enter it every time you connect to the server', default: false, }, { name: 'password', type: 'password', message: 'Enter private key password', when: ({usePassword}) => usePassword, validate: (input) => { if (input.length < 6) { return "Password must be at least 6 characters in length"; } promptedPassword = input; return true; } }, { name: 'passwordRepeat', type: 'password', message: 'Repeat your password', when: ({usePassword}) => usePassword, validate: (input, answers) => { repeatAttempts++; if (input !== answers.password) { if (repeatAttempts == 3) { return true; } return `Passwords do not match! ${3 - repeatAttempts} attempts remaining`; } return true; } } ]) .then(({usePassword, password, passwordRepeat}) => { if (!usePassword) { return false; } if (password !== passwordRepeat) { cli.log('Passwords could not be matched, exiting key generation.'); return Promise.reject('password was not matched'); } return password; }); }) .then((password) => { return new Promise((resolve, reject) => { this.log(`Generating RSA key pair for '${owner}', standby...`); rsa.generateKeyPair({bits: 2048, workers: 2}, function (err, keypair) { if (err) { return reject(err); } return resolve({keypair, password}); }); }); }) .then(({keypair, password}) => { cli.log('Encoding keys'); const privateKeyPem = !password ? pki.privateKeyToPem(keypair.privateKey) : pki.encryptRsaPrivateKey(keypair.privateKey, password); const publicKey = pki.setRsaPublicKey(keypair.privateKey.n, keypair.privateKey.e); const publicKeyPem = pki.publicKeyToPem(publicKey); return {privateKeyPem, publicKeyPem}; }); } function saveKeys(cli, project, {privateKeyPem, publicKeyPem}, owner) { const paths = getKeyPaths(project, owner); cli.log('Writing keys to disk'); fs.outputFileSync(paths.private, privateKeyPem); fs.outputFileSync(paths.public, publicKeyPem); cli.log(`Private key saved to ${paths.private}`); cli.log(`Public key saved to ${paths.public}`); return Promise.resolve(true); } function getSignedJwt(cli, project, owner) { cli.log(`Generating JWT signed by ${owner}`); const paths = getKeyPaths(project, owner); return new Promise((resolve, reject) => { fs.readFile(paths.private, 'utf8', (error, pem) => { if (error) { return reject(`Keys do not exist for ${owner}. You will need to run 'key generate ${owner}' to create the key pair`); } return resolve(pem); }); }) .then((pem) => { if (pem.includes('BEGIN ENCRYPTED PRIVATE KEY')) { return inquirer.prompt([ { name: 'password', type: 'password', message: 'Enter private key passphrase', validate: (input) => { if (!input) { return "This private key is encrypted, you must enter a password"; } try { const privateKey = pki.decryptRsaPrivateKey(pem, input); if (privateKey) { return true; } } catch (e) { } return 'Incorrect password'; } } ]) .then(({password}) => { const privateKey = pki.decryptRsaPrivateKey(pem, password); return pki.privateKeyToPem(privateKey); }); } return Promise.resolve(pem) }) .then((pem) => { const token = jwt.sign( { username: process.env.USER }, pem, { algorithm: 'RS256', }); return { jwt:token, publicKeyPath: paths.public.replace(project.basePath, '.') }; }); } module.exports = {task, getSignedJwt};