http: handle api keys better.
This commit is contained in:
parent
745c12ef3a
commit
323ea3eda5
4
bin/cli
4
bin/cli
@ -506,8 +506,8 @@ CLI.prototype.handleNode = co(function* handleNode() {
|
|||||||
this.log(' $ tx [hash/address]: View transactions.');
|
this.log(' $ tx [hash/address]: View transactions.');
|
||||||
this.log(' $ coin [hash+index/address]: View coins.');
|
this.log(' $ coin [hash+index/address]: View coins.');
|
||||||
this.log(' $ block [hash/height]: View block.');
|
this.log(' $ block [hash/height]: View block.');
|
||||||
this.log(' $ rescan [height/hash]: Rescan for transactions (admin-only).');
|
this.log(' $ rescan [height/hash]: Rescan for transactions.');
|
||||||
this.log(' $ backup [path]: Backup the wallet db (admin-only).');
|
this.log(' $ backup [path]: Backup the wallet db.');
|
||||||
this.log(' $ rpc [command] [args]: Execute RPC command.');
|
this.log(' $ rpc [command] [args]: Execute RPC command.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,7 +58,7 @@ known-peers: ./known-peers
|
|||||||
# ssl-key: @/ssl/priv.key
|
# ssl-key: @/ssl/priv.key
|
||||||
# http-port: 8332
|
# http-port: 8332
|
||||||
# http-host: 0.0.0.0
|
# http-host: 0.0.0.0
|
||||||
|
# service-key: bikeshed
|
||||||
api-key: bikeshed
|
api-key: bikeshed
|
||||||
admin-key: bikeshed2
|
|
||||||
wallet-auth: false
|
wallet-auth: false
|
||||||
# no-auth: false
|
# no-auth: false
|
||||||
|
|||||||
@ -69,11 +69,9 @@ function RPC(node) {
|
|||||||
|
|
||||||
utils.inherits(RPC, EventEmitter);
|
utils.inherits(RPC, EventEmitter);
|
||||||
|
|
||||||
RPC.prototype.execute = function execute(json, admin) {
|
RPC.prototype.execute = function execute(json) {
|
||||||
switch (json.method) {
|
switch (json.method) {
|
||||||
case 'stop':
|
case 'stop':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.stop(json.params);
|
return this.stop(json.params);
|
||||||
case 'help':
|
case 'help':
|
||||||
return this.help(json.params);
|
return this.help(json.params);
|
||||||
@ -109,8 +107,6 @@ RPC.prototype.execute = function execute(json, admin) {
|
|||||||
case 'gettxoutsetinfo':
|
case 'gettxoutsetinfo':
|
||||||
return this.gettxoutsetinfo(json.params);
|
return this.gettxoutsetinfo(json.params);
|
||||||
case 'verifychain':
|
case 'verifychain':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.verifychain(json.params);
|
return this.verifychain(json.params);
|
||||||
|
|
||||||
case 'invalidateblock':
|
case 'invalidateblock':
|
||||||
@ -134,18 +130,12 @@ RPC.prototype.execute = function execute(json, admin) {
|
|||||||
return this.submitblock(json.params);
|
return this.submitblock(json.params);
|
||||||
|
|
||||||
case 'setgenerate':
|
case 'setgenerate':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.setgenerate(json.params);
|
return this.setgenerate(json.params);
|
||||||
case 'getgenerate':
|
case 'getgenerate':
|
||||||
return this.getgenerate(json.params);
|
return this.getgenerate(json.params);
|
||||||
case 'generate':
|
case 'generate':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.generate(json.params);
|
return this.generate(json.params);
|
||||||
case 'generatetoaddress':
|
case 'generatetoaddress':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.generatetoaddress(json.params);
|
return this.generatetoaddress(json.params);
|
||||||
|
|
||||||
case 'estimatefee':
|
case 'estimatefee':
|
||||||
@ -225,14 +215,10 @@ RPC.prototype.execute = function execute(json, admin) {
|
|||||||
case 'addwitnessaddress':
|
case 'addwitnessaddress':
|
||||||
return this.addwitnessaddress(json.params);
|
return this.addwitnessaddress(json.params);
|
||||||
case 'backupwallet':
|
case 'backupwallet':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.backupwallet(json.params);
|
return this.backupwallet(json.params);
|
||||||
case 'dumpprivkey':
|
case 'dumpprivkey':
|
||||||
return this.dumpprivkey(json.params);
|
return this.dumpprivkey(json.params);
|
||||||
case 'dumpwallet':
|
case 'dumpwallet':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.dumpwallet(json.params);
|
return this.dumpwallet(json.params);
|
||||||
case 'encryptwallet':
|
case 'encryptwallet':
|
||||||
return this.encryptwallet(json.params);
|
return this.encryptwallet(json.params);
|
||||||
@ -261,8 +247,6 @@ RPC.prototype.execute = function execute(json, admin) {
|
|||||||
case 'importprivkey':
|
case 'importprivkey':
|
||||||
return this.importprivkey(json.params);
|
return this.importprivkey(json.params);
|
||||||
case 'importwallet':
|
case 'importwallet':
|
||||||
if (!admin)
|
|
||||||
return Promise.resolve('Not authorized.');
|
|
||||||
return this.importwallet(json.params);
|
return this.importwallet(json.params);
|
||||||
case 'importaddress':
|
case 'importaddress':
|
||||||
return this.importaddress(json.params);
|
return this.importaddress(json.params);
|
||||||
|
|||||||
@ -62,28 +62,24 @@ function HTTPServer(options) {
|
|||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.apiKey = options.apiKey;
|
this.apiKey = options.apiKey;
|
||||||
this.apiHash = null;
|
this.apiHash = null;
|
||||||
this.adminHash = null;
|
this.serviceKey = options.serviceKey;
|
||||||
|
this.serviceHash = null;
|
||||||
this.rpc = null;
|
this.rpc = null;
|
||||||
|
|
||||||
if (!this.apiKey)
|
if (!this.apiKey)
|
||||||
this.apiKey = utils.toBase58(crypto.randomBytes(20));
|
this.apiKey = utils.toBase58(crypto.randomBytes(20));
|
||||||
|
|
||||||
|
if (!this.serviceKey)
|
||||||
|
this.serviceKey = this.apiKey;
|
||||||
|
|
||||||
assert(typeof this.apiKey === 'string', 'API key must be a string.');
|
assert(typeof this.apiKey === 'string', 'API key must be a string.');
|
||||||
assert(this.apiKey.length <= 200, 'API key must be under 200 bytes.');
|
assert(this.apiKey.length <= 200, 'API key must be under 200 bytes.');
|
||||||
|
|
||||||
|
assert(typeof this.serviceKey === 'string', 'API key must be a string.');
|
||||||
|
assert(this.serviceKey.length <= 200, 'API key must be under 200 bytes.');
|
||||||
|
|
||||||
this.apiHash = hash256(this.apiKey);
|
this.apiHash = hash256(this.apiKey);
|
||||||
this.adminHash = this.apiHash;
|
this.serviceHash = hash256(this.serviceKey);
|
||||||
|
|
||||||
if (options.adminKey) {
|
|
||||||
assert(typeof options.adminKey === 'string', 'API key must be a string.');
|
|
||||||
assert(options.adminKey.length <= 200, 'API key must be under 200 bytes.');
|
|
||||||
this.adminHash = hash256(options.adminKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.noAuth) {
|
|
||||||
this.apiKey = null;
|
|
||||||
this.apiHash = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.sockets = true;
|
options.sockets = true;
|
||||||
|
|
||||||
@ -143,7 +139,6 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
if (!auth) {
|
if (!auth) {
|
||||||
req.username = null;
|
req.username = null;
|
||||||
req.password = null;
|
req.password = null;
|
||||||
req.admin = false;
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +151,6 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
|
|
||||||
req.username = parts.shift();
|
req.username = parts.shift();
|
||||||
req.password = parts.join(':');
|
req.password = parts.join(':');
|
||||||
req.admin = false;
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
@ -164,21 +158,22 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
this.use(function(req, res, send, next) {
|
this.use(function(req, res, send, next) {
|
||||||
var hash;
|
var hash;
|
||||||
|
|
||||||
if (!this.apiHash) {
|
if (this.options.noAuth)
|
||||||
req.admin = true;
|
|
||||||
return next();
|
return next();
|
||||||
}
|
|
||||||
|
|
||||||
hash = hash256(req.password);
|
hash = hash256(req.password);
|
||||||
|
|
||||||
if (crypto.ccmp(hash, this.adminHash)) {
|
// Regular API key gives access to everything.
|
||||||
req.admin = true;
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (crypto.ccmp(hash, this.apiHash))
|
if (crypto.ccmp(hash, this.apiHash))
|
||||||
return next();
|
return next();
|
||||||
|
|
||||||
|
// If they're hitting the wallet services,
|
||||||
|
// they can use the less powerful API key.
|
||||||
|
if (isWalletPath(req)) {
|
||||||
|
if (crypto.ccmp(hash, this.serviceHash))
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="node"');
|
res.setHeader('WWW-Authenticate', 'Basic realm="node"');
|
||||||
|
|
||||||
if (req.method === 'POST'
|
if (req.method === 'POST'
|
||||||
@ -445,6 +440,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.use(con(function* (req, res, send, next) {
|
this.use(con(function* (req, res, send, next) {
|
||||||
|
var options = req.options;
|
||||||
var wallet;
|
var wallet;
|
||||||
|
|
||||||
if (req.path.length < 2 || req.path[0] !== 'wallet') {
|
if (req.path.length < 2 || req.path[0] !== 'wallet') {
|
||||||
@ -453,7 +449,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.options.walletAuth) {
|
if (!this.options.walletAuth) {
|
||||||
wallet = yield this.walletdb.get(req.options.id);
|
wallet = yield this.walletdb.get(options.id);
|
||||||
|
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
send(404);
|
send(404);
|
||||||
@ -467,7 +463,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wallet = yield this.walletdb.auth(req.options.id, req.options.token);
|
wallet = yield this.walletdb.auth(options.id, options.token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.info('Auth failure for %s: %s.',
|
this.logger.info('Auth failure for %s: %s.',
|
||||||
req.options.id, err.message);
|
req.options.id, err.message);
|
||||||
@ -482,7 +478,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
|
|
||||||
req.wallet = wallet;
|
req.wallet = wallet;
|
||||||
|
|
||||||
this.logger.info('Successful auth for %s.', req.options.id);
|
this.logger.info('Successful auth for %s.', options.id);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}));
|
}));
|
||||||
@ -503,7 +499,7 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
json = yield this.rpc.execute(req.body, req.admin);
|
json = yield this.rpc.execute(req.body);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
|
|
||||||
@ -698,9 +694,6 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
|
|
||||||
enforce(height != null, 'Hash or height is required.');
|
enforce(height != null, 'Hash or height is required.');
|
||||||
|
|
||||||
if (!req.admin)
|
|
||||||
throw new Error('Cannot scan.');
|
|
||||||
|
|
||||||
send(200, { success: true });
|
send(200, { success: true });
|
||||||
yield this.node.scan(height);
|
yield this.node.scan(height);
|
||||||
}));
|
}));
|
||||||
@ -712,9 +705,6 @@ HTTPServer.prototype._init = function _init() {
|
|||||||
|
|
||||||
enforce(path, 'Path is required.');
|
enforce(path, 'Path is required.');
|
||||||
|
|
||||||
if (!req.admin)
|
|
||||||
throw new Error('Cannot backup.');
|
|
||||||
|
|
||||||
yield this.walletdb.backup(path);
|
yield this.walletdb.backup(path);
|
||||||
send(200, { success: true });
|
send(200, { success: true });
|
||||||
}));
|
}));
|
||||||
@ -1091,24 +1081,21 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('auth', function(args, callback) {
|
socket.on('auth', function(args, callback) {
|
||||||
var apiKey = args[0];
|
var key = args[0];
|
||||||
var hash;
|
var hash, api, service;
|
||||||
|
|
||||||
if (socket.auth)
|
if (socket.auth)
|
||||||
return callback({ error: 'Already authed.' });
|
return callback({ error: 'Already authed.' });
|
||||||
|
|
||||||
socket.stop();
|
socket.stop();
|
||||||
|
|
||||||
if (self.apiHash) {
|
if (!self.options.noAuth) {
|
||||||
hash = hash256(apiKey);
|
hash = hash256(key);
|
||||||
if (crypto.ccmp(hash, self.adminHash)) {
|
api = crypto.ccmp(hash, self.apiHash);
|
||||||
socket.admin = true;
|
service = crypto.ccmp(hash, self.serviceHash);
|
||||||
} else {
|
if (!api && !service)
|
||||||
if (!crypto.ccmp(hash, self.apiHash))
|
return callback({ error: 'Bad key.' });
|
||||||
return callback({ error: 'Bad key.' });
|
socket.api = api;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
socket.admin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.auth = true;
|
socket.auth = true;
|
||||||
@ -1170,11 +1157,15 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('watch chain', function(args, callback) {
|
socket.on('watch chain', function(args, callback) {
|
||||||
|
if (!socket.api)
|
||||||
|
return callback({ error: 'Not authorized.' });
|
||||||
socket.watchChain();
|
socket.watchChain();
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('unwatch chain', function(args, callback) {
|
socket.on('unwatch chain', function(args, callback) {
|
||||||
|
if (!socket.api)
|
||||||
|
return callback({ error: 'Not authorized.' });
|
||||||
socket.unwatchChain();
|
socket.unwatchChain();
|
||||||
callback();
|
callback();
|
||||||
});
|
});
|
||||||
@ -1185,6 +1176,9 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
if (!Array.isArray(addresses))
|
if (!Array.isArray(addresses))
|
||||||
return callback({ error: 'Invalid parameter.' });
|
return callback({ error: 'Invalid parameter.' });
|
||||||
|
|
||||||
|
if (!socket.api)
|
||||||
|
return callback({ error: 'Not authorized.' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket.addFilter(addresses);
|
socket.addFilter(addresses);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1200,6 +1194,9 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
if (!Array.isArray(addresses))
|
if (!Array.isArray(addresses))
|
||||||
return callback({ error: 'Invalid parameter.' });
|
return callback({ error: 'Invalid parameter.' });
|
||||||
|
|
||||||
|
if (!socket.api)
|
||||||
|
return callback({ error: 'Not authorized.' });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
socket.removeFilter(addresses);
|
socket.removeFilter(addresses);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1212,12 +1209,12 @@ HTTPServer.prototype._initIO = function _initIO() {
|
|||||||
socket.on('scan chain', function(args, callback) {
|
socket.on('scan chain', function(args, callback) {
|
||||||
var start = args[0];
|
var start = args[0];
|
||||||
|
|
||||||
if (!socket.admin)
|
|
||||||
return callback({ error: 'Cannot scan.' });
|
|
||||||
|
|
||||||
if (!utils.isHex256(start) && !utils.isUInt32(start))
|
if (!utils.isHex256(start) && !utils.isUInt32(start))
|
||||||
return callback({ error: 'Invalid parameter.' });
|
return callback({ error: 'Invalid parameter.' });
|
||||||
|
|
||||||
|
if (!socket.api)
|
||||||
|
return callback({ error: 'Not authorized.' });
|
||||||
|
|
||||||
if (typeof start === 'string')
|
if (typeof start === 'string')
|
||||||
start = utils.revHex(start);
|
start = utils.revHex(start);
|
||||||
|
|
||||||
@ -1276,12 +1273,16 @@ HTTPServer.prototype.open = co(function* open() {
|
|||||||
|
|
||||||
this.logger.info('HTTP server loaded.');
|
this.logger.info('HTTP server loaded.');
|
||||||
|
|
||||||
if (this.apiKey) {
|
if (this.options.noAuth) {
|
||||||
this.logger.info('HTTP API key: %s', this.apiKey);
|
|
||||||
this.apiKey = null;
|
|
||||||
} else if (!this.apiHash) {
|
|
||||||
this.logger.warning('WARNING: Your http server is open to the world.');
|
this.logger.warning('WARNING: Your http server is open to the world.');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.info('HTTP API key: %s', this.apiKey);
|
||||||
|
this.logger.info('HTTP Service API key: %s', this.serviceKey);
|
||||||
|
|
||||||
|
this.apiKey = null;
|
||||||
|
this.serviceKey = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1361,7 +1362,7 @@ function ClientSocket(server, socket) {
|
|||||||
this.auth = false;
|
this.auth = false;
|
||||||
this.filter = {};
|
this.filter = {};
|
||||||
this.filterCount = 0;
|
this.filterCount = 0;
|
||||||
this.admin = false;
|
this.api = false;
|
||||||
|
|
||||||
this.chain = this.server.chain;
|
this.chain = this.server.chain;
|
||||||
this.mempool = this.server.mempool;
|
this.mempool = this.server.mempool;
|
||||||
@ -1650,6 +1651,16 @@ function sortCoins(coins) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isWalletPath(req) {
|
||||||
|
if (req.path.length >= 1 && req.path[0] === 'wallet')
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (req.method === 'GET' && req.pathname === '/')
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Expose
|
* Expose
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -199,7 +199,7 @@ config.parseData = function parseData(data, prefix, dirname) {
|
|||||||
options.httpPort = num(data.httpport);
|
options.httpPort = num(data.httpport);
|
||||||
options.httpHost = str(data.httphost);
|
options.httpHost = str(data.httphost);
|
||||||
options.apiKey = str(data.apikey);
|
options.apiKey = str(data.apikey);
|
||||||
options.adminKey = str(data.adminkey);
|
options.serviceKey = str(data.servicekey);
|
||||||
options.walletAuth = bool(data.walletauth);
|
options.walletAuth = bool(data.walletauth);
|
||||||
options.noAuth = bool(data.noauth);
|
options.noAuth = bool(data.noauth);
|
||||||
|
|
||||||
|
|||||||
@ -162,7 +162,7 @@ function Fullnode(options) {
|
|||||||
port: this.options.httpPort || this.network.rpcPort,
|
port: this.options.httpPort || this.network.rpcPort,
|
||||||
host: this.options.httpHost || '0.0.0.0',
|
host: this.options.httpHost || '0.0.0.0',
|
||||||
apiKey: this.options.apiKey,
|
apiKey: this.options.apiKey,
|
||||||
adminKey: this.options.adminKey,
|
serviceKey: this.options.serviceKey,
|
||||||
walletAuth: this.options.walletAuth,
|
walletAuth: this.options.walletAuth,
|
||||||
noAuth: this.options.noAuth
|
noAuth: this.options.noAuth
|
||||||
});
|
});
|
||||||
|
|||||||
@ -99,7 +99,7 @@ function SPVNode(options) {
|
|||||||
port: this.options.httpPort || this.network.rpcPort,
|
port: this.options.httpPort || this.network.rpcPort,
|
||||||
host: this.options.httpHost || '0.0.0.0',
|
host: this.options.httpHost || '0.0.0.0',
|
||||||
apiKey: this.options.apiKey,
|
apiKey: this.options.apiKey,
|
||||||
adminKey: this.options.adminKey,
|
serviceKey: this.options.serviceKey,
|
||||||
walletAuth: this.options.walletAuth,
|
walletAuth: this.options.walletAuth,
|
||||||
noAuth: this.options.noAuth
|
noAuth: this.options.noAuth
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user