Skip to content

Commit

Permalink
feat: xud master password
Browse files Browse the repository at this point in the history
This is a major commit that introduces new functionality to encrypt the
xud node key with a password which can also be used to encrypt lnd and
(with a separate PR) Raiden.

A new config option `noencrypt` is added and for now defaults to `true`,
meaning that this functionality is off by default. This is to minimize
disruption with existing applications of xud. When `noencrypt` is true,
starting up xud occurs as it does before this PR.

When `noencrypt` is false, we first check if there is an existing node
key file saved to disk. If one exists, we wait to receive a new
`UnlockNode` call to provide us with a password. We use this password to
decrypt the node key file and to call `UnlockWallet` on each configured
lnd instance that is waiting to be unlocked.

If none is found, we wait to receive a new `CreateNode` call to provide
us with a password. We then attempt to call `GenSeed` on a configured
lnd instance to generate an aezeed 24 word mnemonic. We use this
mnemonic to generate our node key, and then encrypt that with our
password and save to disk. We then call `InitWallet` on each configured
lnd instance that is waiting to be unlocked, using the same mnemonic and
password from earlier steps.

As part of this PR, we introduce a new grpc `XudInitService` for calls
when xud is in a state of waiting for a password before it can complete
initialization. New `create` and `unlock` commands are added for
`xucli` as well.

This is a major step towards #912.
  • Loading branch information
sangaman committed Jun 18, 2019
1 parent fd11eef commit 6bfad02
Show file tree
Hide file tree
Showing 32 changed files with 1,657 additions and 1,000 deletions.
5 changes: 5 additions & 0 deletions bin/xud
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ const { argv } = require('yargs')
type: 'boolean',
default: undefined,
},
noencrypt: {
describe: 'Whether to disable xud nodekey encription',
type: 'boolean',
default: undefined,
},
nomatching: {
describe: 'Whether to disable matching within xud',
type: 'boolean',
Expand Down
73 changes: 72 additions & 1 deletion docs/api.md

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

12 changes: 6 additions & 6 deletions lib/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ class Config {
public webproxy: { port: number, disable: boolean };
public instanceid = 0;
/** Whether to intialize a new database with default values. */
public initdb: boolean;
public initdb = true;
/** The file path for the database, or ':memory:' if the database should be kept in memory. */
public dbpath: string;
/** Whether matching will be disabled */
public nomatching: boolean;
public nomatching = false;
/** Whether a password should not be used to encrypt the xud key and underlying wallets. */
public noencrypt = true; // TODO: enable encryption by default

/**
* Whether to disable sanity checks that verify that the orders can possibly be swapped
* before adding them to the order book, can be enabled for testing & debugging purposes.
*/
public nosanitychecks: boolean;
public nosanitychecks = false;

constructor() {
const platform = os.platform();
Expand All @@ -59,14 +62,11 @@ class Config {
}

// default configuration
this.initdb = true;
this.nomatching = false;
this.loglevel = this.getDefaultLogLevel();
this.logpath = this.getDefaultLogPath();
this.logdateformat = 'DD/MM/YYYY HH:mm:ss.SSS';
this.network = this.getDefaultNetwork();
this.dbpath = this.getDefaultDbPath();
this.nosanitychecks = false;

this.p2p = {
listen: true,
Expand Down
47 changes: 35 additions & 12 deletions lib/Xud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { EventEmitter } from 'events';
import Swaps from './swaps/Swaps';
import HttpServer from './http/HttpServer';
import SwapClientManager from './swaps/SwapClientManager';
import InitService from './service/InitService';
import { promises as fs } from 'fs';

const version: string = require('../package.json').version;

Expand Down Expand Up @@ -65,8 +67,35 @@ class Xud extends EventEmitter {
this.db = new DB(loggers.db, this.config.dbpath);
await this.db.init(this.config.network, this.config.initdb);

const nodeKey = await NodeKey.load(this.config.xudir, this.config.instanceid);
this.logger.info(`Local nodePubKey is ${nodeKey.nodePubKey}`);
this.swapClientManager = new SwapClientManager(this.config, loggers);
await this.swapClientManager.init(this.db.models);

const nodeKeyPath = NodeKey.getPath(this.config.xudir, this.config.instanceid);
let nodeKey: NodeKey;
if (this.config.noencrypt) {
nodeKey = await NodeKey.load(nodeKeyPath);
} else {
const nodeKeyExists = await fs.access(nodeKeyPath).then(() => true).catch(() => false);
const initService = new InitService(this.swapClientManager, nodeKeyPath, nodeKeyExists);

const initRpcServer = new GrpcServer(loggers.rpc);
initRpcServer.addXudInitService(initService);
await initRpcServer.listen(
this.config.rpc.port,
this.config.rpc.host,
path.join(this.config.xudir, 'tls.cert'),
path.join(this.config.xudir, 'tls.key'),
);

this.logger.info("Node key is encrypted, unlock using 'xucli unlock' or set password using" +
" 'xucli create' if this is the first time starting xud");
nodeKey = await new Promise<NodeKey>((resolve) => {
initService.once('nodekey', resolve);
});
await initRpcServer.close();
}

this.logger.info(`Local nodePubKey is ${nodeKey.pubKey}`);

this.pool = new Pool({
nodeKey,
Expand Down Expand Up @@ -117,21 +146,15 @@ class Xud extends EventEmitter {

// start rpc server last
if (!this.config.rpc.disable) {
this.rpcServer = new GrpcServer(loggers.rpc, this.service);
const listening = await this.rpcServer.listen(
this.rpcServer = new GrpcServer(loggers.rpc);
this.rpcServer.addXudService(this.service);
await this.rpcServer.listen(
this.config.rpc.port,
this.config.rpc.host,
path.join(this.config.xudir, 'tls.cert'),
path.join(this.config.xudir, 'tls.key'),
);

if (!listening) {
// if rpc should be enabled but fails to start, treat it as a fatal error
this.logger.error('Could not start gRPC server, exiting...');
await this.shutdown();
return;
}

if (!this.config.webproxy.disable) {
this.grpcAPIProxy = new GrpcWebProxyServer(loggers.rpc);
try {
Expand All @@ -149,7 +172,7 @@ class Xud extends EventEmitter {
this.logger.warn('RPC server is disabled.');
}
} catch (err) {
this.logger.error(err);
this.logger.error('Unexpected error during initialization', err);
}
}

Expand Down
44 changes: 26 additions & 18 deletions lib/cli/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,45 @@ import fs from 'fs';
import os from 'os';
import path from 'path';
import grpc from 'grpc';
import { XudClient } from '../proto/xudrpc_grpc_pb';
import { XudClient, XudInitClient } from '../proto/xudrpc_grpc_pb';

function getXudDir() {
switch (os.platform()) {
case 'win32': {
const homeDir = process.env.LOCALAPPDATA!;
return path.join(homeDir, 'Xud');
}
case 'darwin': {
const homeDir = process.env.HOME!;
return path.join(homeDir, '.xud');
}
default: {
const homeDir = process.env.HOME!;
return path.join(homeDir, '.xud');
}
}
}

/**
* A generic function to instantiate an XU client.
* @param argv the command line arguments
*/
export const loadXudClient = (argv: Arguments) => {
const getXudDir = () => {
switch (os.platform()) {
case 'win32': {
const homeDir = process.env.LOCALAPPDATA!;
return path.join(homeDir, 'Xud');
}
case 'darwin': {
const homeDir = process.env.HOME!;
return path.join(homeDir, '.xud');
}
default: {
const homeDir = process.env.HOME!;
return path.join(homeDir, '.xud');
}
}
};

const certPath = argv.tlscertpath ? argv.tlscertpath : path.join(getXudDir(), 'tls.cert');
const cert = fs.readFileSync(certPath);
const credentials = grpc.credentials.createSsl(cert);

return new XudClient(`${argv.rpchost}:${argv.rpcport}`, credentials);
};

export const loadXudInitClient = (argv: Arguments) => {
const certPath = argv.tlscertpath ? argv.tlscertpath : path.join(getXudDir(), 'tls.cert');
const cert = fs.readFileSync(certPath);
const credentials = grpc.credentials.createSsl(cert);

return new XudInitClient(`${argv.rpchost}:${argv.rpcport}`, credentials);
};

interface GrpcResponse {
toObject: Function;
}
Expand Down
20 changes: 20 additions & 0 deletions lib/cli/commands/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Arguments } from 'yargs';
import { callback, loadXudInitClient } from '../command';
import { CreateNodeRequest } from '../../proto/xudrpc_pb';

export const command = 'create <password>';

export const describe = 'create an xud node';

export const builder = {
password: {
description: 'password to encrypt xud key and wallets',
type: 'string',
},
};

export const handler = (argv: Arguments) => {
const request = new CreateNodeRequest();
request.setPassword(argv.password);
loadXudInitClient(argv).createNode(request, callback(argv));
};
20 changes: 20 additions & 0 deletions lib/cli/commands/unlock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Arguments } from 'yargs';
import { callback, loadXudInitClient } from '../command';
import { UnlockNodeRequest } from '../../proto/xudrpc_pb';

export const command = 'unlock <password>';

export const describe = 'unlock an xud node';

export const builder = {
password: {
description: 'password to decrypt xud key and wallets',
type: 'string',
},
};

export const handler = (argv: Arguments) => {
const request = new UnlockNodeRequest();
request.setPassword(argv.password);
loadXudInitClient(argv).unlockNode(request, callback(argv));
};
Loading

0 comments on commit 6bfad02

Please sign in to comment.