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

View File

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

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.
* @returns {Object}

View File

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