bip150: add dns resolution. comments.
This commit is contained in:
parent
1e9b7363c2
commit
491462f551
@ -18,47 +18,57 @@ var ec = require('../crypto/ec');
|
||||
var StaticWriter = require('../utils/staticwriter');
|
||||
var base58 = require('../utils/base58');
|
||||
var encoding = require('../utils/encoding');
|
||||
var IP = require('../utils/ip');
|
||||
var dns = require('./dns');
|
||||
|
||||
/**
|
||||
* Represents a BIP150 input and output stream.
|
||||
* Represents a BIP150 input/output stream.
|
||||
* @exports BIP150
|
||||
* @constructor
|
||||
* @param {BIP151} bip151
|
||||
* @param {String} host
|
||||
* @param {Boolean} outbound
|
||||
* @param {AuthDB} db
|
||||
* @param {Buffer} key - Identity key.
|
||||
* @property {BIP151} bip151
|
||||
* @property {BIP151Stream} input
|
||||
* @property {BIP151Stream} output
|
||||
* @property {String} hostname
|
||||
* @property {Boolean} outbound
|
||||
* @property {AuthDB} db
|
||||
* @property {Buffer} privateKey
|
||||
* @property {Buffer} publicKey
|
||||
* @property {Buffer} peerIdentity
|
||||
* @property {Boolean} challengeReceived
|
||||
* @property {Boolean} replyReceived
|
||||
* @property {Boolean} proposeReceived
|
||||
* @property {Boolean} challengeSent
|
||||
* @property {Boolean} auth
|
||||
* @property {Boolean} completed
|
||||
*/
|
||||
|
||||
function BIP150(bip151, hostname, outbound, db, identity) {
|
||||
function BIP150(bip151, host, outbound, db, key) {
|
||||
if (!(this instanceof BIP150))
|
||||
return new BIP150(bip151, hostname, outbound, db, identity);
|
||||
|
||||
assert(bip151, 'BIP150 requires BIP151.');
|
||||
assert(typeof hostname === 'string', 'Hostname required.');
|
||||
assert(typeof outbound === 'boolean', 'Outbound flag required.');
|
||||
assert(db instanceof AuthDB, 'Auth DB required.');
|
||||
assert(Buffer.isBuffer(identity), 'Identity key required.');
|
||||
return new BIP150(bip151, host, outbound, db, key);
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
assert(bip151, 'BIP150 requires BIP151.');
|
||||
assert(typeof host === 'string', 'Hostname required.');
|
||||
assert(typeof outbound === 'boolean', 'Outbound flag required.');
|
||||
assert(db instanceof AuthDB, 'Auth DB required.');
|
||||
assert(Buffer.isBuffer(key), 'Identity key required.');
|
||||
|
||||
this.bip151 = bip151;
|
||||
this.input = bip151.input;
|
||||
this.output = bip151.output;
|
||||
|
||||
this.hostname = hostname; // ip & port
|
||||
|
||||
this.db = db;
|
||||
this.hostname = host;
|
||||
this.outbound = outbound;
|
||||
this.db = db;
|
||||
this.privateKey = key;
|
||||
this.publicKey = ec.publicKeyCreate(key, true);
|
||||
|
||||
this.peerIdentity = null;
|
||||
|
||||
if (this.outbound)
|
||||
this.peerIdentity = this.db.getKnown(this.hostname);
|
||||
|
||||
// Identity keypair
|
||||
this.privateKey = identity;
|
||||
this.publicKey = ec.publicKeyCreate(identity, true);
|
||||
|
||||
this.challengeReceived = false;
|
||||
this.replyReceived = false;
|
||||
this.proposeReceived = false;
|
||||
@ -67,16 +77,44 @@ function BIP150(bip151, hostname, outbound, db, identity) {
|
||||
this.completed = false;
|
||||
this.job = null;
|
||||
this.timeout = null;
|
||||
this.onAuth = null;
|
||||
|
||||
this._init();
|
||||
}
|
||||
|
||||
util.inherits(BIP150, EventEmitter);
|
||||
|
||||
/**
|
||||
* Initialize BIP150.
|
||||
* @private
|
||||
*/
|
||||
|
||||
BIP150.prototype._init = function _init() {
|
||||
if (this.outbound)
|
||||
this.peerIdentity = this.db.getKnown(this.hostname);
|
||||
};
|
||||
|
||||
/**
|
||||
* Test whether the state should be
|
||||
* considered authed. This differs
|
||||
* for inbound vs. outbound.
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
|
||||
BIP150.prototype.isAuthed = function isAuthed() {
|
||||
if (this.outbound)
|
||||
return this.challengeSent && this.challengeReceived;
|
||||
return this.challengeReceived && this.replyReceived;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a received challenge hash.
|
||||
* Returns an authreply signature.
|
||||
* @param {Buffer} hash
|
||||
* @returns {Buffer}
|
||||
* @throws on auth failure
|
||||
*/
|
||||
|
||||
BIP150.prototype.challenge = function challenge(hash) {
|
||||
var type = this.outbound ? 'r' : 'i';
|
||||
var msg, sig;
|
||||
@ -104,6 +142,14 @@ BIP150.prototype.challenge = function challenge(hash) {
|
||||
return ec.fromDER(sig);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a received reply signature.
|
||||
* Returns an authpropose hash.
|
||||
* @param {Buffer} data
|
||||
* @returns {Buffer}
|
||||
* @throws on auth failure
|
||||
*/
|
||||
|
||||
BIP150.prototype.reply = function reply(data) {
|
||||
var type = this.outbound ? 'i' : 'r';
|
||||
var sig, msg, result;
|
||||
@ -138,6 +184,13 @@ BIP150.prototype.reply = function reply(data) {
|
||||
return this.hash(this.input.sid, 'p', this.publicKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle a received propose hash.
|
||||
* Returns an authchallenge hash.
|
||||
* @param {Buffer} hash
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
BIP150.prototype.propose = function propose(hash) {
|
||||
var match;
|
||||
|
||||
@ -162,6 +215,13 @@ BIP150.prototype.propose = function propose(hash) {
|
||||
return this.hash(this.output.sid, 'r', this.peerIdentity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create initial authchallenge hash
|
||||
* for the peer. The peer's identity
|
||||
* key must be known.
|
||||
* @returns {AuthChallengePacket}
|
||||
*/
|
||||
|
||||
BIP150.prototype.toChallenge = function toChallenge() {
|
||||
var msg;
|
||||
|
||||
@ -177,6 +237,17 @@ BIP150.prototype.toChallenge = function toChallenge() {
|
||||
return new packets.AuthChallengePacket(msg);
|
||||
};
|
||||
|
||||
/**
|
||||
* Derive new cipher keys based on
|
||||
* BIP150 data. This differs from
|
||||
* the regular key derivation of BIP151.
|
||||
* @param {Buffer} sid - Sesson ID
|
||||
* @param {Buffer} key - `k1` or `k2`
|
||||
* @param {Buffer} req - Requesting Identity Key
|
||||
* @param {Buffer} res - Response Identity Key
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
BIP150.prototype.rekey = function rekey(sid, key, req, res) {
|
||||
var seed = new Buffer(130);
|
||||
sid.copy(seed, 0);
|
||||
@ -186,6 +257,11 @@ BIP150.prototype.rekey = function rekey(sid, key, req, res) {
|
||||
return crypto.hash256(seed);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rekey the BIP151 input stream
|
||||
* using BIP150-style derivation.
|
||||
*/
|
||||
|
||||
BIP150.prototype.rekeyInput = function rekeyInput() {
|
||||
var stream = this.input;
|
||||
var req = this.peerIdentity;
|
||||
@ -195,6 +271,11 @@ BIP150.prototype.rekeyInput = function rekeyInput() {
|
||||
stream.rekey(k1, k2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Rekey the BIP151 output stream
|
||||
* using BIP150-style derivation.
|
||||
*/
|
||||
|
||||
BIP150.prototype.rekeyOutput = function rekeyOutput() {
|
||||
var stream = this.output;
|
||||
var req = this.publicKey;
|
||||
@ -204,6 +285,14 @@ BIP150.prototype.rekeyOutput = function rekeyOutput() {
|
||||
stream.rekey(k1, k2);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a hash using the session ID.
|
||||
* @param {Buffer} sid
|
||||
* @param {String} ch
|
||||
* @param {Buffer} key
|
||||
* @returns {Buffer}
|
||||
*/
|
||||
|
||||
BIP150.prototype.hash = function hash(sid, ch, key) {
|
||||
var data = new Buffer(66);
|
||||
sid.copy(data, 0);
|
||||
@ -212,6 +301,16 @@ BIP150.prototype.hash = function hash(sid, ch, key) {
|
||||
return crypto.hash256(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Find an authorized peer in the Auth
|
||||
* DB based on a proposal hash. Note
|
||||
* that the hash to find is specific
|
||||
* to the state of BIP151. This results
|
||||
* in an O(n) search.
|
||||
* @param {Buffer} hash
|
||||
* @returns {Buffer|null}
|
||||
*/
|
||||
|
||||
BIP150.prototype.findAuthorized = function findAuthorized(hash) {
|
||||
var i, key, msg;
|
||||
|
||||
@ -228,13 +327,29 @@ BIP150.prototype.findAuthorized = function findAuthorized(hash) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the BIP150 stream and
|
||||
* any current running wait job.
|
||||
*/
|
||||
|
||||
BIP150.prototype.destroy = function destroy() {
|
||||
if (this.timeout != null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
|
||||
if (this.onAuth) {
|
||||
this.removeListener('auth', this.onAuth);
|
||||
this.onAuth = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cleanup wait job.
|
||||
* @private
|
||||
* @returns {Job}
|
||||
*/
|
||||
|
||||
BIP150.prototype.cleanup = function cleanup(err) {
|
||||
var job = this.job;
|
||||
|
||||
@ -249,19 +364,42 @@ BIP150.prototype.cleanup = function cleanup(err) {
|
||||
this.timeout = null;
|
||||
}
|
||||
|
||||
if (this.onAuth) {
|
||||
this.removeListener('auth', this.onAuth);
|
||||
this.onAuth = null;
|
||||
}
|
||||
|
||||
return job;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the current wait job.
|
||||
* @private
|
||||
* @param {Object} result
|
||||
*/
|
||||
|
||||
BIP150.prototype.resolve = function resolve(result) {
|
||||
var job = this.cleanup();
|
||||
job.resolve(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reject the current wait job.
|
||||
* @private
|
||||
* @param {Error} err
|
||||
*/
|
||||
|
||||
BIP150.prototype.reject = function reject(err) {
|
||||
var job = this.cleanup();
|
||||
job.reject(err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for handshake to complete.
|
||||
* @param {Number} timeout
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
BIP150.prototype.wait = function wait(timeout) {
|
||||
var self = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
@ -269,6 +407,14 @@ BIP150.prototype.wait = function wait(timeout) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for handshake to complete.
|
||||
* @private
|
||||
* @param {Number} timeout
|
||||
* @param {Function} resolve
|
||||
* @param {Function} reject
|
||||
*/
|
||||
|
||||
BIP150.prototype._wait = function wait(timeout, resolve, reject) {
|
||||
var self = this;
|
||||
|
||||
@ -276,23 +422,36 @@ BIP150.prototype._wait = function wait(timeout, resolve, reject) {
|
||||
|
||||
this.job = co.job(resolve, reject);
|
||||
|
||||
if (this.outbound && !this.peerIdentity)
|
||||
return this.reject(new Error('No identity for ' + this.hostname + '.'));
|
||||
if (this.outbound && !this.peerIdentity) {
|
||||
this.reject(new Error('No identity for ' + this.hostname + '.'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.timeout = setTimeout(function() {
|
||||
self.reject(new Error('BIP150 handshake timed out.'));
|
||||
}, timeout);
|
||||
|
||||
this.once('auth', function() {
|
||||
self.resolve();
|
||||
});
|
||||
this.onAuth = this.resolve.bind(this);
|
||||
this.once('auth', this.onAuth);
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize the peer's identity
|
||||
* key as a BIP150 "address".
|
||||
* @returns {Base58String}
|
||||
*/
|
||||
|
||||
BIP150.prototype.getAddress = function getAddress() {
|
||||
assert(this.peerIdentity, 'Cannot serialize address.');
|
||||
return BIP150.address(this.peerIdentity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialize an identity key as a
|
||||
* BIP150 "address".
|
||||
* @returns {Base58String}
|
||||
*/
|
||||
|
||||
BIP150.address = function address(key) {
|
||||
var bw = new StaticWriter(27);
|
||||
bw.writeU8(0x0f);
|
||||
@ -312,16 +471,41 @@ function AuthDB(options) {
|
||||
if (!(this instanceof AuthDB))
|
||||
return new AuthDB(options);
|
||||
|
||||
this.logger = null;
|
||||
this.resolve = dns.resolve;
|
||||
this.proxyServer = null;
|
||||
|
||||
this.known = {};
|
||||
this.authorized = [];
|
||||
this.dns = [];
|
||||
|
||||
this._init(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize authdb with options.
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
AuthDB.prototype._init = function _init(options) {
|
||||
if (!options)
|
||||
return;
|
||||
|
||||
if (options.logger != null) {
|
||||
assert(typeof options.logger === 'object');
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
if (options.resolve != null) {
|
||||
assert(typeof options.resolve === 'function');
|
||||
this.resolve = options.resolve;
|
||||
}
|
||||
|
||||
if (options.proxyServer != null) {
|
||||
assert(typeof options.proxyServer === 'string');
|
||||
this.proxyServer = options.proxyServer;
|
||||
}
|
||||
|
||||
if (options.knownPeers != null) {
|
||||
assert(typeof options.knownPeers === 'object');
|
||||
this.setKnown(options.knownPeers);
|
||||
@ -333,19 +517,46 @@ AuthDB.prototype._init = function _init(options) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a known peer.
|
||||
* @param {String} host - Peer Hostname
|
||||
* @param {Buffer} key - Identity Key
|
||||
*/
|
||||
|
||||
AuthDB.prototype.addKnown = function addKnown(host, key) {
|
||||
var addr;
|
||||
|
||||
assert(typeof host === 'string');
|
||||
assert(Buffer.isBuffer(key) && key.length === 33,
|
||||
'Invalid public key for known peer.');
|
||||
|
||||
addr = IP.parseHost(host);
|
||||
|
||||
// Defer this for resolution.
|
||||
if (addr.version === -1) {
|
||||
this.dns.push([addr, key]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.known[host] = key;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an authorized peer.
|
||||
* @param {Buffer} key - Identity Key
|
||||
*/
|
||||
|
||||
AuthDB.prototype.addAuthorized = function addAuthorized(key) {
|
||||
assert(Buffer.isBuffer(key) && key.length === 33,
|
||||
'Invalid public key for authorized peer.');
|
||||
this.authorized.push(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize known peers with a host->key map.
|
||||
* @param {Object} map
|
||||
*/
|
||||
|
||||
AuthDB.prototype.setKnown = function setKnown(map) {
|
||||
var keys = Object.keys(map);
|
||||
var i, host, key;
|
||||
@ -357,6 +568,11 @@ AuthDB.prototype.setKnown = function setKnown(map) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize authorized peers with a list of keys.
|
||||
* @param {Buffer[]} keys
|
||||
*/
|
||||
|
||||
AuthDB.prototype.setAuthorized = function setAuthorized(keys) {
|
||||
var i, key;
|
||||
|
||||
@ -366,10 +582,76 @@ AuthDB.prototype.setAuthorized = function setAuthorized(keys) {
|
||||
}
|
||||
};
|
||||
|
||||
AuthDB.prototype.getKnown = function getKnown(host) {
|
||||
return this.known[host];
|
||||
/**
|
||||
* Get a known peer key by hostname.
|
||||
* @param {String} hostname
|
||||
* @returns {Buffer|null}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.getKnown = function getKnown(hostname) {
|
||||
var known = this.known[hostname];
|
||||
var addr;
|
||||
|
||||
if (known)
|
||||
return known;
|
||||
|
||||
addr = IP.parseHost(hostname);
|
||||
|
||||
return this.known[addr.host];
|
||||
};
|
||||
|
||||
/**
|
||||
* Lookup any dns-based known peers.
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.discover = co(function* discover() {
|
||||
var jobs = [];
|
||||
var i, addr;
|
||||
|
||||
for (i = 0; i < this.dns.length; i++) {
|
||||
addr = this.dns[i];
|
||||
jobs.push(this.populate(addr[0], addr[1]));
|
||||
}
|
||||
|
||||
this.dns.length = 0;
|
||||
|
||||
yield Promise.all(jobs);
|
||||
});
|
||||
|
||||
/**
|
||||
* Populate known peers with a dns-based host.
|
||||
* @private
|
||||
* @param {Object} addr
|
||||
* @param {Buffer} key
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
AuthDB.prototype.populate = co(function* populate(addr, key) {
|
||||
var i, hosts, host;
|
||||
|
||||
assert(addr.version === -1);
|
||||
|
||||
if (this.logger)
|
||||
this.logger.info('Resolving authorized hosts from: %s.', addr.host);
|
||||
|
||||
try {
|
||||
hosts = yield this.resolve(addr.host, this.proxyServer);
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < hosts.length; i++) {
|
||||
host = hosts[i];
|
||||
|
||||
if (addr.port !== 0)
|
||||
host = IP.hostname(host, addr.port);
|
||||
|
||||
this.known[host] = key;
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Expose
|
||||
*/
|
||||
|
||||
@ -11,13 +11,13 @@ describe('BIP150', function() {
|
||||
var sk = ec.generatePrivateKey();
|
||||
|
||||
db.addAuthorized(ec.publicKeyCreate(ck, true));
|
||||
db.addKnown('server', ec.publicKeyCreate(sk, true));
|
||||
db.addKnown('127.0.0.2', ec.publicKeyCreate(sk, true));
|
||||
|
||||
var client = new BIP151();
|
||||
var server = new BIP151();
|
||||
|
||||
client.bip150 = new BIP150(client, 'server', true, db, ck);
|
||||
server.bip150 = new BIP150(server, 'client', false, db, sk);
|
||||
client.bip150 = new BIP150(client, '127.0.0.2', true, db, ck);
|
||||
server.bip150 = new BIP150(server, '127.0.0.1', false, db, sk);
|
||||
|
||||
function payload() {
|
||||
return new Buffer('deadbeef', 'hex');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user