UNPKG

balanceofsatoshis

Version:
316 lines (259 loc) 8.96 kB
const asyncAuto = require('async/auto'); const {decodeFirst} = require('cbor'); const {returnResult} = require('asyncjs-util'); const sanitize = require('sanitize-filename'); const {derAsPem} = require('./../encryption'); const {homePath} = require('../storage'); const putSavedCredentials = require('./put_saved_credentials'); const credentialsFileName = 'credentials.json'; const defaultHost = 'localhost'; const defaultRpcPort = 10009; const keyType = 'rsa'; const modulusLength = 4096; const privateKeyEncoding = {format: 'pem', type: 'pkcs8'}; const publicKeyEncoding = {format: 'der', type: 'spki'}; /** Add or update a saved node { ask: <Inquirer Function> ({message, name, type}, cbk) => {} cryptography: { generateKeyPair: <Generate Key Pair Function> (keyType, options) => {} } fs: { makeDirectory: <Make Directory Function> writeFile: <Write File Function> } logger: <Winston Logger Object> [node]: <Node Name String> } @returns via cbk or Promise */ module.exports = ({ask, cryptography, fs, logger, node}, cbk) => { return new Promise((resolve, reject) => { return asyncAuto({ // Check arguments validate: cbk => { if (!ask) { return cbk([400, 'ExpectedAskFunctionToRegisterSavedNode']); } if (!cryptography) { return cbk([400, 'ExpectedCryptographyFunctionsToRegisterNode']); } if (!fs) { return cbk([400, 'ExpectedFileSystemMethodsToRegisterSavedNode']); } if (!logger) { return cbk([400, 'ExpectedLoggerToRegisterSavedNode']); } return cbk(); }, // Make sure the home directory is there registerHomeDirectory: ['validate', ({}, cbk) => { return fs.makeDirectory(homePath({}).path, err => { // Ignore errors, the directory may already be there return cbk(); }); }], // Start import startImport: ['validate', ({}, cbk) => { const cmd = 'bos credentials'; const start = { message: `Step 1. Run "${cmd}" on the node to add. Continue?`, name: 'start', type: 'confirm', }; return ask(start, ({start}) => { if (!start) { return cbk([400, 'CanceledNodeRegistration']); } return cbk(); }); }], // Generate transfer key transferKey: ['validate', ({}, cbk) => { return cryptography.generateKeyPair(keyType, { modulusLength, privateKeyEncoding, publicKeyEncoding, }, (err, publicKey, privateKey) => { if (!!err) { return cbk([503, 'FailedToGenerateCredentialsKey', {err}]); } return cbk(null, {private_key: privateKey, public_key: publicKey}); }); }], // Copy travel key copyKey: ['startImport', 'transferKey', ({transferKey}, cbk) => { const key = Buffer.from(transferKey.public_key).toString('hex'); logger.info({credentials_transfer_public_key: key}); const copyPrompt = { message: `Step 2. Enter the transfer key on target node. Continue?"`, name: 'copied', type: 'confirm', }; return ask(copyPrompt, ({copied}) => { if (!copied) { return cbk([400, 'CanceledNodeRegistration']); } return cbk(); }); }], // Enter CBOR encoded credentials enterCredentials: ['copyKey', 'transferKey', ({transferKey}, cbk) => { const credentialsPrompt = { message: 'Step 3: Enter the credentials you got after pasting:', name: 'credentials', type: 'input', }; return ask(credentialsPrompt, ({credentials}) => { if (!credentials) { return cbk([400, 'ExpectedCredentialsForRemoteNode']); } return cbk(null, credentials); }); }], // Decode credentials decodeCredentials: ['enterCredentials', ({enterCredentials}, cbk) => { const encoded = Buffer.from(enterCredentials, 'hex'); return decodeFirst(encoded, (err, node) => { if (!!err) { return cbk([400, 'ExpectedValidEncodedCredentials', {err}]); } if (!node.cert) { return cbk([400, 'ExpectedTlsCertInCredentials']); } if (!node.encrypted_macaroon) { return cbk([400, 'ExpectedEncryptedMacaroonInCredentials']); } const {pem} = derAsPem({cert: node.cert}); return cbk(null, { cert: Buffer.from(pem).toString('base64'), encrypted_macaroon: node.encrypted_macaroon, socket: node.socket || undefined, }); }); }], // Socket details socket: ['decodeCredentials', ({decodeCredentials}, cbk) => { if (!decodeCredentials.socket) { return cbk(null, {host: defaultHost, port: defaultRpcPort}); } try { const url = new URL(`rpc://${decodeCredentials.socket}`); return cbk(null, {host: url.hostname, port: Number(url.port)}); } catch (err) { return cbk([400, 'ExpectedStandardSocketInCredentials', {err}]); } }], // Enter Host enterHost: ['socket', ({socket}, cbk) => { const hostPrompt = { default: socket.host, message: `Node RPC host (defaults to: ${socket.host})`, name: 'host', type: 'input', }; return ask(hostPrompt, ({host}) => { if (!host) { return cbk([400, 'ExpectedHostForNodeCredentials']); } return cbk(null, host); }); }], // Enter Port enterPort: ['enterHost', 'socket', ({socket}, cbk) => { const defaultPort = socket.port || defaultRpcPort; const portPrompt = { default: defaultPort, message: `Node RPC port (defaults to: ${defaultPort})`, name: 'port', type: 'number', }; return ask(portPrompt, ({port}) => { if (!port) { return cbk([400, 'ExpectedPortForNodeCredentials']); } return cbk(null, port); }); }], // Decrypt macaroon decryptMacaroon: [ 'decodeCredentials', 'transferKey', ({decodeCredentials, transferKey}, cbk) => { const encryptedMacaroon = decodeCredentials.encrypted_macaroon; const privateKey = transferKey.private_key; const cipher = Buffer.from(encryptedMacaroon, 'base64'); try { return cbk(null, cryptography.privateDecrypt(privateKey, cipher)); } catch (err) { return cbk([400, 'FailedToDecryptNodeMacaroon', {err}]); } }], // Node details details: [ 'decodeCredentials', 'decryptMacaroon', 'enterHost', 'enterPort', ({decodeCredentials, decryptMacaroon, enterHost, enterPort}, cbk) => { const rawCert = Buffer.from(decodeCredentials.cert.toString('base64')) .toString(); const rawCertLines = Buffer.from(rawCert, 'base64').toString() .split('\n'); const cert = Buffer.from(rawCertLines.join('\n') + '\n') .toString('base64'); const macaroon = decryptMacaroon.toString('base64'); const socket = `${enterHost}:${enterPort}`; return cbk(null, {cert, macaroon, socket}); }], // Name for node nodeName: ['details', ({details}, cbk) => { if (!!node) { return cbk(null, node); } const namePrompt = { message: 'Name for this node?', name: 'moniker', type: 'input', }; return ask(namePrompt, ({moniker}) => { if (!moniker) { return cbk([400, 'CanceledNodeRegistration']); } const sanitized = sanitize(moniker); if (moniker !== sanitized) { return cbk([400, 'InvalidNameForNode', {suggested: sanitized}]); } return cbk(null, moniker); }); }], // Make sure the node directory is there nodeDir: [ 'details', 'nodeName', 'registerHomeDirectory', ({nodeName}, cbk) => { return fs.makeDirectory(homePath({file: nodeName}).path, err => { // Ignore errors, the directory may already be there return cbk(); }); }], // Save details save: ['details', 'nodeName', 'nodeDir', ({details, nodeName}, cbk) => { return putSavedCredentials({ fs, cert: details.cert, macaroon: details.macaroon, node: nodeName, socket: details.socket, }, cbk); }], }, returnResult({reject, resolve}, cbk)); }); };