http: better handling of api keys.

This commit is contained in:
Christopher Jeffrey 2017-08-01 01:38:36 -07:00
parent 1a4268544f
commit 1d7b8ca7c1
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD
4 changed files with 59 additions and 45 deletions

View File

@ -181,27 +181,22 @@ HTTPBase.prototype.cors = function cors() {
*/ */
HTTPBase.prototype.basicAuth = function basicAuth(options) { HTTPBase.prototype.basicAuth = function basicAuth(options) {
assert(options, 'Basic auth requires options.');
let user = options.username; let user = options.username;
let pass = options.password; let pass = options.password;
let realm = options.realm; let realm = options.realm;
if (user) { if (user != null) {
if (typeof user === 'string') { assert(typeof user === 'string');
assert(user.length <= 255, 'Username too long.');
user = Buffer.from(user, 'utf8');
}
assert(Buffer.isBuffer(user));
assert(user.length <= 255, 'Username too long.'); assert(user.length <= 255, 'Username too long.');
assert(util.isAscii(user), 'Username must be ASCII.');
user = digest.hash256(user); user = digest.hash256(user);
} }
if (typeof pass === 'string') { assert(typeof pass === 'string');
assert(pass.length <= 255, 'Password too long.');
pass = Buffer.from(pass, 'utf8');
}
assert(Buffer.isBuffer(pass));
assert(pass.length <= 255, 'Password too long.'); assert(pass.length <= 255, 'Password too long.');
assert(util.isAscii(pass), 'Password must be ASCII.');
pass = digest.hash256(pass); pass = digest.hash256(pass);
if (!realm) if (!realm)
@ -209,11 +204,11 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
assert(typeof realm === 'string'); assert(typeof realm === 'string');
function fail(res) { const fail = (res) => {
res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`); res.setHeader('WWW-Authenticate', `Basic realm="${realm}"`);
res.setStatus(401); res.setStatus(401);
res.end(); res.end();
} };
return async (req, res) => { return async (req, res) => {
const hdr = req.headers['authorization']; const hdr = req.headers['authorization'];
@ -223,7 +218,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return; return;
} }
if (hdr.length > 1000) { if (hdr.length > 674) {
fail(res); fail(res);
return; return;
} }
@ -235,12 +230,14 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return; return;
} }
if (parts[0] !== 'Basic') { const [type, b64] = parts;
if (type !== 'Basic') {
fail(res); fail(res);
return; return;
} }
const auth = Buffer.from(parts[1], 'base64').toString('utf8'); const auth = Buffer.from(b64, 'base64').toString('ascii');
const items = auth.split(':'); const items = auth.split(':');
const username = items.shift(); const username = items.shift();
@ -252,7 +249,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return; return;
} }
const raw = Buffer.from(username, 'utf8'); const raw = Buffer.from(username, 'ascii');
const hash = digest.hash256(raw); const hash = digest.hash256(raw);
if (!ccmp(hash, user)) { if (!ccmp(hash, user)) {
@ -266,7 +263,7 @@ HTTPBase.prototype.basicAuth = function basicAuth(options) {
return; return;
} }
const raw = Buffer.from(password, 'utf8'); const raw = Buffer.from(password, 'ascii');
const hash = digest.hash256(raw); const hash = digest.hash256(raw);
if (!ccmp(hash, pass)) { if (!ccmp(hash, pass)) {
@ -316,16 +313,19 @@ HTTPBase.prototype.parseBody = async function parseBody(req, options) {
if (req.method === 'GET') if (req.method === 'GET')
return body; return body;
const data = await this.readBody(req, 'utf8', options);
if (!data)
return body;
let type = req.contentType; let type = req.contentType;
if (options.contentType) if (options.contentType)
type = options.contentType; type = options.contentType;
if (type === 'bin')
return body;
const data = await this.readBody(req, 'utf8', options);
if (!data)
return body;
switch (type) { switch (type) {
case 'json': case 'json':
body = JSON.parse(data); body = JSON.parse(data);
@ -333,8 +333,6 @@ HTTPBase.prototype.parseBody = async function parseBody(req, options) {
case 'form': case 'form':
body = parsePairs(data, options.keyLimit); body = parsePairs(data, options.keyLimit);
break; break;
default:
break;
} }
return body; return body;

View File

@ -370,7 +370,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) {
if (key.length > 255) if (key.length > 255)
throw new Error('Invalid API key.'); throw new Error('Invalid API key.');
const data = Buffer.from(key, 'utf8'); const data = Buffer.from(key, 'ascii');
const hash = digest.hash256(data); const hash = digest.hash256(data);
if (!ccmp(hash, this.options.apiHash)) if (!ccmp(hash, this.options.apiHash))
@ -701,7 +701,7 @@ function HTTPOptions(options) {
this.logger = null; this.logger = null;
this.node = null; this.node = null;
this.apiKey = base58.encode(random.randomBytes(20)); this.apiKey = base58.encode(random.randomBytes(20));
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8')); this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
this.noAuth = false; this.noAuth = false;
this.prefix = null; this.prefix = null;
@ -742,8 +742,10 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) {
'API key must be a string.'); 'API key must be a string.');
assert(options.apiKey.length <= 255, assert(options.apiKey.length <= 255,
'API key must be under 256 bytes.'); 'API key must be under 256 bytes.');
assert(util.isAscii(options.apiKey),
'API key must be ascii.');
this.apiKey = options.apiKey; this.apiKey = options.apiKey;
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8')); this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
} }
if (options.noAuth != null) { if (options.noAuth != null) {

View File

@ -772,6 +772,17 @@ if (!''.startsWith) {
}; };
} }
/**
* Test whether a string is a plain
* ascii string (no control characters).
* @param {String} str
* @returns {Boolean}
*/
util.isAscii = function isAscii(str) {
return /^[\t\n\r -~]*$/.test(str);
};
/** /**
* Get memory usage info. * Get memory usage info.
* @returns {Object} * @returns {Object}

View File

@ -395,12 +395,11 @@ HTTPServer.prototype.initRouter = function initRouter() {
for (const output of outputs) { for (const output of outputs) {
const valid = new Validator([output]); const valid = new Validator([output]);
const raw = valid.buf('script');
let script = null;
let script; if (raw)
if (valid.has('script')) {
const raw = valid.buf('script');
script = Script.fromRaw(raw); script = Script.fromRaw(raw);
}
options.outputs.push({ options.outputs.push({
script: script, script: script,
@ -434,12 +433,11 @@ HTTPServer.prototype.initRouter = function initRouter() {
for (const output of outputs) { for (const output of outputs) {
const valid = new Validator([output]); const valid = new Validator([output]);
const raw = valid.buf('script');
let script = null;
let script; if (raw)
if (valid.has('script')) {
const raw = valid.buf('script');
script = Script.fromRaw(raw); script = Script.fromRaw(raw);
}
options.outputs.push({ options.outputs.push({
script: script, script: script,
@ -698,12 +696,13 @@ HTTPServer.prototype.initRouter = function initRouter() {
const valid = req.valid(); const valid = req.valid();
const acct = valid.str('account'); const acct = valid.str('account');
const txs = await req.wallet.getHistory(acct); const txs = await req.wallet.getHistory(acct);
const result = [];
common.sortTX(txs); common.sortTX(txs);
const details = await req.wallet.toDetails(txs); const details = await req.wallet.toDetails(txs);
const result = [];
for (const item of details) for (const item of details)
result.push(item.toJSON()); result.push(item.toJSON());
@ -715,12 +714,13 @@ HTTPServer.prototype.initRouter = function initRouter() {
const valid = req.valid(); const valid = req.valid();
const acct = valid.str('account'); const acct = valid.str('account');
const txs = await req.wallet.getPending(acct); const txs = await req.wallet.getPending(acct);
const result = [];
common.sortTX(txs); common.sortTX(txs);
const details = await req.wallet.toDetails(txs); const details = await req.wallet.toDetails(txs);
const result = [];
for (const item of details) for (const item of details)
result.push(item.toJSON()); result.push(item.toJSON());
@ -731,7 +731,6 @@ HTTPServer.prototype.initRouter = function initRouter() {
this.get('/:id/tx/range', async (req, res) => { this.get('/:id/tx/range', async (req, res) => {
const valid = req.valid(); const valid = req.valid();
const acct = valid.str('account'); const acct = valid.str('account');
const result = [];
const options = { const options = {
start: valid.u32('start'), start: valid.u32('start'),
@ -744,6 +743,8 @@ HTTPServer.prototype.initRouter = function initRouter() {
const details = await req.wallet.toDetails(txs); const details = await req.wallet.toDetails(txs);
const result = [];
for (const item of details) for (const item of details)
result.push(item.toJSON()); result.push(item.toJSON());
@ -852,7 +853,7 @@ HTTPServer.prototype.handleSocket = function handleSocket(socket) {
if (!this.options.noAuth) { if (!this.options.noAuth) {
const valid = new Validator([args]); const valid = new Validator([args]);
const key = valid.str(0); const key = valid.str(0, '');
if (key.length > 255) if (key.length > 255)
throw new Error('Invalid API key.'); throw new Error('Invalid API key.');
@ -943,7 +944,7 @@ function HTTPOptions(options) {
this.logger = null; this.logger = null;
this.walletdb = null; this.walletdb = null;
this.apiKey = base58.encode(random.randomBytes(20)); this.apiKey = base58.encode(random.randomBytes(20));
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8')); this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
this.serviceHash = this.apiHash; this.serviceHash = this.apiHash;
this.noAuth = false; this.noAuth = false;
this.walletAuth = false; this.walletAuth = false;
@ -983,10 +984,12 @@ HTTPOptions.prototype.fromOptions = function fromOptions(options) {
if (options.apiKey != null) { if (options.apiKey != null) {
assert(typeof options.apiKey === 'string', assert(typeof options.apiKey === 'string',
'API key must be a string.'); 'API key must be a string.');
assert(options.apiKey.length <= 200, assert(options.apiKey.length <= 255,
'API key must be under 200 bytes.'); 'API key must be under 255 bytes.');
assert(util.isAscii(options.apiKey),
'API key must be ASCII.');
this.apiKey = options.apiKey; this.apiKey = options.apiKey;
this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'utf8')); this.apiHash = digest.hash256(Buffer.from(this.apiKey, 'ascii'));
} }
if (options.noAuth != null) { if (options.noAuth != null) {