Socket
Socket
Sign inDemoInstall

libp2p-secio

Package Overview
Dependencies
Maintainers
2
Versions
36
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

libp2p-secio - npm Package Compare versions

Comparing version 0.3.1 to 0.4.2

.tern-port

36

lib/etm.js
'use strict';
var through = require('through2');
var lpm = require('length-prefixed-stream');
var through = require('pull-through');
var pull = require('pull-stream');
var lp = require('pull-length-prefixed');
var toForgeBuffer = require('./support').toForgeBuffer;
exports.writer = function etmWriter(insecure, cipher, mac) {
var encode = lpm.encode();
var pt = through(function (chunk, enc, cb) {
exports.createBoxStream = function (cipher, mac) {
var pt = through(function (chunk) {
cipher.update(toForgeBuffer(chunk));

@@ -18,18 +18,13 @@

this.push(Buffer.concat([data, macBuffer]));
this.queue(Buffer.concat([data, macBuffer]));
// reset hmac
mac.start(null, null);
}
cb();
});
pt.pipe(encode).pipe(insecure);
return pt;
return pull(pt, lp.encode());
};
exports.reader = function etmReader(insecure, decipher, mac) {
var decode = lpm.decode();
var pt = through(function (chunk, enc, cb) {
exports.createUnboxStream = function (decipher, mac) {
var pt = through(function (chunk) {
var l = chunk.length;

@@ -39,3 +34,3 @@ var macSize = mac.getMac().length();

if (l < macSize) {
return cb(new Error('buffer (' + l + ') shorter than MAC size (' + macSize + ')'));
return this.emit('error', new Error('buffer (' + l + ') shorter than MAC size (' + macSize + ')'));
}

@@ -52,6 +47,7 @@

var expected = new Buffer(mac.getMac().getBytes(), 'binary');
// reset hmac
mac.start(null, null);
if (!macd.equals(expected)) {
return cb(new Error('MAC Invalid: ' + macd.toString('hex') + ' != ' + expected.toString('hex')));
return this.emit('error', new Error('MAC Invalid: ' + macd.toString('hex') + ' != ' + expected.toString('hex')));
}

@@ -64,11 +60,7 @@

var _data = new Buffer(decipher.output.getBytes(), 'binary');
this.push(_data);
this.queue(_data);
}
cb();
});
insecure.pipe(decode).pipe(pt);
return pt;
return pull(lp.decode(), pt);
};
'use strict';
var crypto = require('libp2p-crypto');
var debug = require('debug');
var fs = require('fs');
var path = require('path');
var protobuf = require('protocol-buffers');
var support = require('../support');
var crypto = require('./crypto');
var log = debug('libp2p:secio');
log.error = debug('libp2p:secio:error');
var pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')));
var support = require('../support');
// step 2. Exchange
// -- exchange (signed) ephemeral keys. verify signatures.
module.exports = function exchange(session, cb) {
module.exports = function exchange(state, cb) {
log('2. exchange - start');
var genSharedKey = void 0;
var exchangeOut = void 0;
try {
var eResult = crypto.generateEphemeralKeyPair(session.local.curveT);
session.local.ephemeralPubKey = eResult.key;
genSharedKey = eResult.genSharedKey;
exchangeOut = makeExchange(session);
} catch (err) {
return cb(err);
}
session.insecureLp.write(exchangeOut);
session.insecureLp.once('data', function (chunk) {
var exchangeIn = pbm.Exchange.decode(chunk);
try {
verify(session, exchangeIn);
keys(session, exchangeIn, genSharedKey);
macAndCipher(session);
} catch (err) {
log('2. exchange - writing exchange');
support.write(state, crypto.createExchange(state), function (err, size) {
if (err) {
return cb(err);
}
log('2. exchange - finish');
cb();
});
};
support.read(state, null, function (err, msg) {
if (err) {
return cb(err);
}
function makeExchange(session) {
// Gather corpus to sign.
var selectionOut = Buffer.concat([session.proposal.out, session.proposal.in, session.local.ephemeralPubKey]);
log('2. exchange - reading exchange');
var epubkey = session.local.ephemeralPubKey;
var signature = new Buffer(session.localKey.sign(selectionOut), 'binary');
log('out', { epubkey: epubkey, signature: signature });
return pbm.Exchange.encode({ epubkey: epubkey, signature: signature });
}
try {
crypto.verify(state, msg);
crypto.generateKeys(state);
} catch (err) {
return cb(err);
}
function verify(session, exchangeIn) {
log('2.1. verify', exchangeIn);
session.remote.ephemeralPubKey = exchangeIn.epubkey;
var selectionIn = Buffer.concat([session.proposal.in, session.proposal.out, session.remote.ephemeralPubKey]);
var sigOk = session.remote.permanentPubKey.verify(selectionIn, exchangeIn.signature);
if (!sigOk) {
throw new Error('Bad signature');
}
log('2.1. verify - signature verified');
}
function keys(session, exchangeIn, genSharedKey) {
log('2.2. keys');
session.sharedSecret = genSharedKey(exchangeIn.epubkey);
var keys = crypto.keyStretcher(session.local.cipherT, session.local.hashT, session.sharedSecret);
// use random nonces to decide order.
if (session.proposal.order > 0) {
session.local.keys = keys.k1;
session.remote.keys = keys.k2;
} else if (session.proposal.order < 0) {
// swap
session.local.keys = keys.k2;
session.remote.keys = keys.k1;
} else {
// we should've bailed before this. but if not, bail here.
throw new Error('you are trying to talk to yourself');
}
}
function macAndCipher(session) {
log('2.3. mac + cipher');
support.makeMacAndCipher(session.local);
support.makeMacAndCipher(session.remote);
}
log('2. exchange - finish');
cb();
});
});
};
'use strict';
var duplexify = require('duplexify');
var pull = require('pull-stream');
var handshake = require('pull-handshake');
var debug = require('debug');
var log = debug('libp2p:secio');
log.error = debug('libp2p:secio:error');
var read = require('async-buffered-reader');
var etm = require('../etm');
var crypto = require('./crypto');
// step 3. Finish
// -- send expected message to verify encryption works (send local nonce)
module.exports = function finish(session, cb) {
module.exports = function finish(state, cb) {
log('3. finish - start');
var w = etm.writer(session.insecure, session.local.cipher, session.local.mac);
var r = etm.reader(session.insecure, session.remote.cipher, session.remote.mac);
session.secure = duplexify(w, r);
session.secure.write(session.proposal.randIn);
var proto = state.protocols;
var stream = state.shake.rest();
var shake = handshake({ timeout: state.timeout });
// read our nonce back
read(session.secure, 16, function (nonceOut2) {
var nonceOut = session.proposal.nonceOut;
if (!nonceOut.equals(nonceOut2)) {
var err = new Error('Failed to read our encrypted nonce: ' + nonceOut.toString('hex') + ' != ' + nonceOut2.toString('hex'));
log.error(err);
pull(stream, etm.createUnboxStream(proto.local.cipher, proto.local.mac), shake, etm.createBoxStream(proto.remote.cipher, proto.remote.mac), stream);
shake.handshake.write(state.proposal.in.rand);
shake.handshake.read(state.proposal.in.rand.length, function (err, nonceBack) {
if (err) {
return cb(err);
}
log('3. finish - finish', nonceOut.toString('hex'), nonceOut2.toString('hex'));
try {
crypto.verifyNonce(state, nonceBack);
} catch (err) {
state.secure.resolve(pull.error(err));
return cb(err);
}
// Awesome that's all folks.
cb();
state.secure.resolve(shake.handshake.rest());
cb(null);
});
};
'use strict';
var debug = require('debug');
var series = require('run-series');
var log = debug('libp2p:secio');
log.error = debug('libp2p:secio:error');
var propose = require('./propose');

@@ -15,10 +11,18 @@ var exchange = require('./exchange');

// keys, IDs, and initiate communication, assigning all necessary params.
module.exports = function handshake(session, cb) {
module.exports = function handshake(state) {
series([function (cb) {
return propose(session, cb);
return propose(state, cb);
}, function (cb) {
return exchange(session, cb);
return exchange(state, cb);
}, function (cb) {
return finish(session, cb);
}], cb);
return finish(state, cb);
}], function (err) {
state.cleanSecrets();
if (err) {
state.shake.abort(err);
}
});
return state.stream;
};
'use strict';
var forge = require('node-forge');
var debug = require('debug');
var protobuf = require('protocol-buffers');
var path = require('path');
var fs = require('fs');
var PeerId = require('peer-id');
var mh = require('multihashing');
var crypto = require('libp2p-crypto');
var support = require('../support');
var crypto = require('./crypto');
var log = debug('libp2p:secio');
log.error = debug('libp2p:secio:error');
// nonceSize is the size of our nonces (in bytes)
var nonceSize = 16;
var pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')));
var support = require('../support');
// step 1. Propose
// -- propose cipher suite + send pubkeys + nonce
module.exports = function propose(session, cb) {
module.exports = function propose(state, cb) {
log('1. propose - start');
var nonceOut = new Buffer(forge.random.getBytesSync(nonceSize), 'binary');
var proposeOut = makeProposal(session, nonceOut);
session.proposal.out = proposeOut;
session.proposal.nonceOut = nonceOut;
log('1. propse - writing proposal');
session.insecureLp.write(proposeOut);
session.insecureLp.once('data', function (chunk) {
log('1. propse - reading proposal');
var proposeIn = void 0;
try {
proposeIn = readProposal(chunk);
session.proposal.in = chunk;
session.proposal.randIn = proposeIn.rand;
identify(session, proposeIn);
} catch (err) {
support.write(state, crypto.createProposal(state), function (err, size) {
if (err) {
return cb(err);
}
try {
selection(session, nonceOut, proposeIn);
} catch (err) {
return cb(err);
}
support.read(state, size, function (err, msg) {
if (err) {
return cb(err);
}
log('1. propose - finish');
log('1. propse - reading proposal', msg);
cb();
});
};
try {
crypto.identify(state, msg);
crypto.selectProtocols(state);
} catch (err) {
return cb(err);
}
// Generate and send Hello packet.
// Hello = (rand, PublicKey, Supported)
function makeProposal(session, nonceOut) {
session.local.permanentPubKey = session.localKey.public;
var myPubKeyBytes = session.local.permanentPubKey.bytes;
log('1. propose - finish');
return pbm.Propose.encode({
rand: nonceOut,
pubkey: myPubKeyBytes,
exchanges: support.exchanges.join(','),
ciphers: support.ciphers.join(','),
hashes: support.hashes.join(',')
cb();
});
});
}
function readProposal(bytes) {
return pbm.Propose.decode(bytes);
}
function identify(session, proposeIn) {
log('1.1 identify');
session.remote.permanentPubKey = crypto.unmarshalPublicKey(proposeIn.pubkey);
session.remotePeer = PeerId.createFromPubKey(proposeIn.pubkey.toString('base64'));
log('1.1 identify - %s - identified remote peer as %s', session.localPeer.toB58String(), session.remotePeer.toB58String());
}
function selection(session, nonceOut, proposeIn) {
log('1.2 selection');
var local = {
pubKeyBytes: session.local.permanentPubKey.bytes,
exchanges: support.exchanges,
hashes: support.hashes,
ciphers: support.ciphers,
nonce: nonceOut
};
var remote = {
pubKeyBytes: proposeIn.pubkey,
exchanges: proposeIn.exchanges.split(','),
hashes: proposeIn.hashes.split(','),
ciphers: proposeIn.ciphers.split(','),
nonce: proposeIn.rand
};
var selected = selectBest(local, remote);
session.proposal.order = selected.order;
session.local.curveT = selected.curveT;
session.local.cipherT = selected.cipherT;
session.local.hashT = selected.hashT;
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
session.remote.curveT = session.local.curveT;
session.remote.cipherT = session.local.cipherT;
session.remote.hashT = session.local.hashT;
}
function selectBest(local, remote) {
var oh1 = digest(Buffer.concat([remote.pubKeyBytes, local.nonce]));
var oh2 = digest(Buffer.concat([local.pubKeyBytes, remote.nonce]));
var order = Buffer.compare(oh1, oh2);
if (order === 0) {
throw new Error('you are trying to talk to yourself');
}
return {
curveT: support.theBest(order, local.exchanges, remote.exchanges),
cipherT: support.theBest(order, local.ciphers, remote.ciphers),
hashT: support.theBest(order, local.hashes, remote.hashes),
order: order
};
}
function digest(buf) {
return mh.digest(buf, 'sha2-256', buf.length);
}
};

@@ -7,7 +7,8 @@ 'use strict';

var duplexify = require('duplexify');
var lpstream = require('length-prefixed-stream');
var PassThrough = require('readable-stream').PassThrough;
var pull = require('pull-stream');
var toPull = require('stream-to-pull-stream');
var toStream = require('pull-stream-to-stream');
var _handshake = require('./handshake');
var handshake = require('./handshake');
var State = require('./state');

@@ -18,22 +19,7 @@ exports.SecureSession = function () {

this.localKey = key;
this.localPeer = local;
this.sharedSecret = null;
this.local = {};
this.remote = {};
this.proposal = {};
this.insecure = insecure;
this.secure = null;
var e = lpstream.encode();
var d = lpstream.decode();
this.insecureLp = duplexify(e, d);
e.pipe(this.insecure);
this.insecure.pipe(d);
if (!this.localPeer) {
if (!local) {
throw new Error('no local id provided');
}
if (!this.localKey) {
if (!key) {
throw new Error('no local private key provided');

@@ -50,2 +36,6 @@ }

}
this.state = new State(local, key);
pull(toPull.source(insecure), handshake(this.state), toPull.sink(insecure));
}

@@ -56,81 +46,4 @@

value: function secureStream() {
var _this = this;
var handshaked = false;
var reader = new PassThrough();
var writer = new PassThrough();
var dp = duplexify(writer, reader);
var originalRead = reader.read.bind(reader);
var originalWrite = writer.write.bind(writer);
var doHandshake = function doHandshake() {
if (handshaked) return;
handshaked = true;
// Restore methods to avoid overhead
reader.read = originalRead;
writer.write = originalWrite;
_this.handshake(function (err) {
if (err) {
dp.emit('error', err);
}
// Pipe things together
writer.pipe(_this.secure);
_this.secure.pipe(reader);
dp.uncork();
dp.resume();
});
};
// patch to detect first read
reader.read = function (size) {
doHandshake();
originalRead(size);
};
// patch to detect first write
writer.write = function (chunk, encoding, callback) {
doHandshake();
originalWrite(chunk, encoding, callback);
};
dp.cork();
dp.pause();
return dp;
return toStream(this.state.secure);
}
}, {
key: 'handshake',
value: function handshake(cb) {
var _this2 = this;
// TODO: figure out how to best handle the handshake timeout
if (this._handshakeLock) {
return cb(new Error('handshake already in progress'));
}
this._handshakeLock = true;
var finish = function finish(err) {
_this2._handshakeLock = false;
cb(err);
};
if (this._handshakeDone) {
return finish();
}
_handshake(this, function (err) {
if (err) {
return finish(err);
}
_this2._handshakeDone = true;
finish();
});
}
}]);

@@ -137,0 +50,0 @@

'use strict';
var mh = require('multihashing');
var forge = require('node-forge');
var lp = require('pull-length-prefixed');
var pull = require('pull-stream');

@@ -119,2 +122,60 @@ exports.exchanges = ['P-256', 'P-384', 'P-521'];

throw new Error('unrecognized cipher type: ' + cipherType);
}
}
exports.randomBytes = function (nonceSize) {
return new Buffer(forge.random.getBytesSync(nonceSize), 'binary');
};
exports.selectBest = function (local, remote) {
var oh1 = exports.digest(Buffer.concat([remote.pubKeyBytes, local.nonce]));
var oh2 = exports.digest(Buffer.concat([local.pubKeyBytes, remote.nonce]));
var order = Buffer.compare(oh1, oh2);
if (order === 0) {
throw new Error('you are trying to talk to yourself');
}
return {
curveT: exports.theBest(order, local.exchanges, remote.exchanges),
cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
hashT: exports.theBest(order, local.hashes, remote.hashes),
order: order
};
};
exports.digest = function (buf) {
return mh.digest(buf, 'sha2-256', buf.length);
};
exports.write = function write(state, msg, cb) {
pull(pull.values([msg]), lp.encode(), pull.collect(function (err, res) {
if (err) {
return cb(err);
}
state.shake.write(res[0]);
cb(null, res[0].length);
}));
};
exports.read = function read(state, size, cb) {
state.shake.read(size, handleRead);
function handleRead(err, msg) {
if (!err && !msg) {
return state.shake.read(null, handleRead);
}
if (err) {
return cb(err);
}
pull(pull.values([msg]), lp.decode(), pull.collect(function (err, res) {
if (err) {
return cb(err);
}
cb(null, res[0]);
}));
}
};
{
"name": "libp2p-secio",
"version": "0.3.1",
"version": "0.4.2",
"description": "Secio implementation in JavaScript",

@@ -28,23 +28,23 @@ "main": "lib/index.js",

"dependencies": {
"async-buffered-reader": "^1.2.1",
"debug": "^2.2.0",
"duplexify": "^3.4.3",
"length-prefixed-stream": "^1.5.0",
"interface-connection": "^0.2.1",
"libp2p-crypto": "^0.5.0",
"multihashing": "^0.2.1",
"node-forge": "^0.6.39",
"peer-id": "^0.6.7",
"node-forge": "^0.6.42",
"peer-id": "^0.7.0",
"protocol-buffers": "^3.1.6",
"readable-stream": "1.1.13",
"run-series": "^1.1.4",
"through2": "^2.0.1"
"pull-defer": "^0.2.2",
"pull-handshake": "^1.1.4",
"pull-length-prefixed": "^1.2.0",
"pull-stream": "^3.4.3",
"pull-through": "^1.0.18",
"run-series": "^1.1.4"
},
"devDependencies": {
"aegir": "^3.1.0",
"bl": "^1.1.2",
"aegir": "^8.0.0",
"chai": "^3.5.0",
"multistream-select": "^0.9.0",
"multistream-select": "^0.11.0",
"pre-commit": "^1.1.3",
"run-parallel": "^1.1.6",
"stream-pair": "^1.0.3"
"pull-pair": "^1.1.0",
"run-parallel": "^1.1.6"
},

@@ -67,4 +67,7 @@ "pre-commit": [

"contributors": [
"dignifiedquire <dignifiedquire@gmail.com>"
"David Dias <daviddias.p@gmail.com>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"greenkeeperio-bot <support@greenkeeper.io>"
]
}

@@ -6,14 +6,82 @@ # js-libp2p-secio

[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![Coverage Status](https://coveralls.io/repos/github/ipfs/js-libp2p-secio/badge.svg?branch=master)](https://coveralls.io/github/ipfs/js-libp2p-secio?branch=master)
[![Travis CI](https://travis-ci.org/ipfs/js-libp2p-secio.svg?branch=master)](https://travis-ci.org/ipfs/js-libp2p-secio)
[![Circle CI](https://circleci.com/gh/ipfs/js-libp2p-secio.svg?style=svg)](https://circleci.com/gh/ipfs/js-libp2p-secio)
[![Dependency Status](https://david-dm.org/ipfs/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/ipfs/js-libp2p-secio) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![Coverage Status](https://coveralls.io/repos/github/libp2p/js-libp2p-secio/badge.svg?branch=master)](https://coveralls.io/github/libp2p/js-libp2p-secio?branch=master)
[![Travis CI](https://travis-ci.org/libp2p/js-libp2p-secio.svg?branch=master)](https://travis-ci.org/libp2p/js-libp2p-secio)
[![Circle CI](https://circleci.com/gh/libp2p/js-libp2p-secio.svg?style=svg)](https://circleci.com/gh/libp2p/js-libp2p-secio)
[![Dependency Status](https://david-dm.org/libp2p/js-libp2p-secio.svg?style=flat-square)](https://david-dm.org/libp2p/js-libp2p-secio) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
> Secio implementation in JavaScript
## Description
This repo contains the JavaScript implementation of secio, an encryption protocol used in libp2p. This is based on this [go implementation](https://github.com/libp2p/go-libp2p-secio).
This repo contains the JavaScript implementation of secio, an encryption protocol used in libp2p. This is based on this [go implementation](https://github.com/ipfs/go-libp2p-secio).
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Contribute](#contribute)
- [License](#license)
## Install
```sh
npm install libp2p-secio
```
## Usage
```js
const secio = require('libp2p-secio')
```
## API
### `SecureSession`
#### `constructor(id, key, insecure)`
- `id: PeerId` - The id of the node.
- `key: RSAPrivateKey` - The private key of the node.
- `insecure: PullStream` - The insecure connection.
### `.secure`
Returns the `insecure` connection provided, wrapped with secio. This is a pull-stream.
### This module uses `pull-streams`
We expose a streaming interface based on `pull-streams`, rather then on the Node.js core streams implementation (aka Node.js streams). `pull-streams` offers us a better mechanism for error handling and flow control guarantees. If you would like to know more about why we did this, see the discussion at this [issue](https://github.com/ipfs/js-ipfs/issues/362).
You can learn more about pull-streams at:
- [The history of Node.js streams, nodebp April 2014](https://www.youtube.com/watch?v=g5ewQEuXjsQ)
- [The history of streams, 2016](http://dominictarr.com/post/145135293917/history-of-streams)
- [pull-streams, the simple streaming primitive](http://dominictarr.com/post/149248845122/pull-streams-pull-streams-are-a-very-simple)
- [pull-streams documentation](https://pull-stream.github.io/)
#### Converting `pull-streams` to Node.js Streams
If you are a Node.js streams user, you can convert a pull-stream to a Node.js stream using the module [`pull-stream-to-stream`](https://github.com/dominictarr/pull-stream-to-stream), giving you an instance of a Node.js stream that is linked to the pull-stream. For example:
```js
const pullToStream = require('pull-stream-to-stream')
const nodeStreamInstance = pullToStream(pullStreamInstance)
// nodeStreamInstance is an instance of a Node.js Stream
```
To learn more about this utility, visit https://pull-stream.github.io/#pull-stream-to-stream.
## Contribute
Feel free to join in. All welcome. Open an [issue](https://github.com/ipfs/js-libp2p-secio/issues)!
This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)
## License
[MIT](LICENSE)
'use strict'
const through = require('through2')
const lpm = require('length-prefixed-stream')
const through = require('pull-through')
const pull = require('pull-stream')
const lp = require('pull-length-prefixed')
const toForgeBuffer = require('./support').toForgeBuffer
exports.writer = function etmWriter (insecure, cipher, mac) {
const encode = lpm.encode()
const pt = through(function (chunk, enc, cb) {
const lpOpts = {
fixed: true,
bytes: 4
}
exports.createBoxStream = (cipher, mac) => {
const pt = through(function (chunk) {
cipher.update(toForgeBuffer(chunk))

@@ -15,21 +20,19 @@

const data = new Buffer(cipher.output.getBytes(), 'binary')
mac.update(data)
const macBuffer = new Buffer(mac.getMac().getBytes(), 'binary')
mac.update(data.toString('binary'))
const macBuffer = new Buffer(mac.digest().getBytes(), 'binary')
this.push(Buffer.concat([data, macBuffer]))
this.queue(Buffer.concat([data, macBuffer]))
// reset hmac
mac.start(null, null)
}
cb()
})
pt.pipe(encode).pipe(insecure)
return pt
return pull(
pt,
lp.encode(lpOpts)
)
}
exports.reader = function etmReader (insecure, decipher, mac) {
const decode = lpm.decode()
const pt = through(function (chunk, enc, cb) {
exports.createUnboxStream = (decipher, mac) => {
const pt = through(function (chunk) {
const l = chunk.length

@@ -39,3 +42,3 @@ const macSize = mac.getMac().length()

if (l < macSize) {
return cb(new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
return this.emit('error', new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
}

@@ -50,8 +53,9 @@

mac.update(data)
mac.update(data.toString('binary'))
const expected = new Buffer(mac.getMac().getBytes(), 'binary')
// reset hmac
mac.start(null, null)
if (!macd.equals(expected)) {
return cb(new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
return this.emit('error', new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
}

@@ -64,11 +68,10 @@

const data = new Buffer(decipher.output.getBytes(), 'binary')
this.push(data)
this.queue(data)
}
cb()
})
insecure.pipe(decode).pipe(pt)
return pt
return pull(
lp.decode(lpOpts),
pt
)
}
'use strict'
const crypto = require('libp2p-crypto')
const debug = require('debug')
const fs = require('fs')
const path = require('path')
const protobuf = require('protocol-buffers')
const support = require('../support')
const crypto = require('./crypto')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')))
const support = require('../support')
// step 2. Exchange
// -- exchange (signed) ephemeral keys. verify signatures.
module.exports = function exchange (session, cb) {
module.exports = function exchange (state, cb) {
log('2. exchange - start')
let genSharedKey
let exchangeOut
log('2. exchange - writing exchange')
support.write(state, crypto.createExchange(state))
support.read(state.shake, (err, msg) => {
if (err) {
return cb(err)
}
try {
const eResult = crypto.generateEphemeralKeyPair(session.local.curveT)
session.local.ephemeralPubKey = eResult.key
genSharedKey = eResult.genSharedKey
exchangeOut = makeExchange(session)
} catch (err) {
return cb(err)
}
log('2. exchange - reading exchange')
session.insecureLp.write(exchangeOut)
session.insecureLp.once('data', (chunk) => {
const exchangeIn = pbm.Exchange.decode(chunk)
try {
verify(session, exchangeIn)
keys(session, exchangeIn, genSharedKey)
macAndCipher(session)
crypto.verify(state, msg)
crypto.generateKeys(state)
} catch (err) {

@@ -49,61 +36,1 @@ return cb(err)

}
function makeExchange (session) {
// Gather corpus to sign.
const selectionOut = Buffer.concat([
session.proposal.out,
session.proposal.in,
session.local.ephemeralPubKey
])
const epubkey = session.local.ephemeralPubKey
const signature = new Buffer(session.localKey.sign(selectionOut), 'binary')
log('out', {epubkey, signature})
return pbm.Exchange.encode({epubkey, signature})
}
function verify (session, exchangeIn) {
log('2.1. verify', exchangeIn)
session.remote.ephemeralPubKey = exchangeIn.epubkey
const selectionIn = Buffer.concat([
session.proposal.in,
session.proposal.out,
session.remote.ephemeralPubKey
])
const sigOk = session.remote.permanentPubKey.verify(selectionIn, exchangeIn.signature)
if (!sigOk) {
throw new Error('Bad signature')
}
log('2.1. verify - signature verified')
}
function keys (session, exchangeIn, genSharedKey) {
log('2.2. keys')
session.sharedSecret = genSharedKey(exchangeIn.epubkey)
const keys = crypto.keyStretcher(session.local.cipherT, session.local.hashT, session.sharedSecret)
// use random nonces to decide order.
if (session.proposal.order > 0) {
session.local.keys = keys.k1
session.remote.keys = keys.k2
} else if (session.proposal.order < 0) {
// swap
session.local.keys = keys.k2
session.remote.keys = keys.k1
} else {
// we should've bailed before this. but if not, bail here.
throw new Error('you are trying to talk to yourself')
}
}
function macAndCipher (session) {
log('2.3. mac + cipher')
support.makeMacAndCipher(session.local)
support.makeMacAndCipher(session.remote)
}
'use strict'
const duplexify = require('duplexify')
const pull = require('pull-stream')
const handshake = require('pull-handshake')
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
const read = require('async-buffered-reader')
const etm = require('../etm')
const crypto = require('./crypto')
// step 3. Finish
// -- send expected message to verify encryption works (send local nonce)
module.exports = function finish (session, cb) {
module.exports = function finish (state, cb) {
log('3. finish - start')
const w = etm.writer(session.insecure, session.local.cipher, session.local.mac)
const r = etm.reader(session.insecure, session.remote.cipher, session.remote.mac)
session.secure = duplexify(w, r)
session.secure.write(session.proposal.randIn)
const proto = state.protocols
const stream = state.shake.rest()
const shake = handshake({timeout: state.timeout})
// read our nonce back
read(session.secure, 16, (nonceOut2) => {
const nonceOut = session.proposal.nonceOut
if (!nonceOut.equals(nonceOut2)) {
const err = new Error(`Failed to read our encrypted nonce: ${nonceOut.toString('hex')} != ${nonceOut2.toString('hex')}`)
pull(
stream,
etm.createUnboxStream(proto.remote.cipher, proto.remote.mac),
shake,
etm.createBoxStream(proto.local.cipher, proto.local.mac),
stream
)
shake.handshake.write(state.proposal.in.rand)
shake.handshake.read(state.proposal.in.rand.length, (err, nonceBack) => {
const fail = (err) => {
log.error(err)
return cb(err)
state.secure.resolve({
source: pull.error(err),
sink (read) {
}
})
cb(err)
}
log('3. finish - finish', nonceOut.toString('hex'), nonceOut2.toString('hex'))
if (err) return fail(err)
try {
crypto.verifyNonce(state, nonceBack)
} catch (err) {
return fail(err)
}
log('3. finish - finish')
// Awesome that's all folks.
state.secure.resolve(shake.handshake.rest())
cb()
})
}
'use strict'
const debug = require('debug')
const series = require('run-series')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
const propose = require('./propose')

@@ -15,8 +11,16 @@ const exchange = require('./exchange')

// keys, IDs, and initiate communication, assigning all necessary params.
module.exports = function handshake (session, cb) {
module.exports = function handshake (state) {
series([
(cb) => propose(session, cb),
(cb) => exchange(session, cb),
(cb) => finish(session, cb)
], cb)
(cb) => propose(state, cb),
(cb) => exchange(state, cb),
(cb) => finish(state, cb)
], (err) => {
state.cleanSecrets()
if (err) {
state.shake.abort(err)
}
})
return state.stream
}
'use strict'
const forge = require('node-forge')
const debug = require('debug')
const protobuf = require('protocol-buffers')
const path = require('path')
const fs = require('fs')
const PeerId = require('peer-id')
const mh = require('multihashing')
const crypto = require('libp2p-crypto')
const support = require('../support')
const crypto = require('./crypto')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')
// nonceSize is the size of our nonces (in bytes)
const nonceSize = 16
const pbm = protobuf(fs.readFileSync(path.join(__dirname, '../secio.proto')))
const support = require('../support')
// step 1. Propose
// -- propose cipher suite + send pubkeys + nonce
module.exports = function propose (session, cb) {
module.exports = function propose (state, cb) {
log('1. propose - start')
const nonceOut = new Buffer(forge.random.getBytesSync(nonceSize), 'binary')
const proposeOut = makeProposal(session, nonceOut)
session.proposal.out = proposeOut
session.proposal.nonceOut = nonceOut
log('1. propse - writing proposal')
session.insecureLp.write(proposeOut)
session.insecureLp.once('data', (chunk) => {
log('1. propse - reading proposal')
let proposeIn
try {
proposeIn = readProposal(chunk)
session.proposal.in = chunk
session.proposal.randIn = proposeIn.rand
identify(session, proposeIn)
} catch (err) {
log('1. propose - writing proposal')
support.write(state, crypto.createProposal(state))
support.read(state.shake, (err, msg) => {
if (err) {
return cb(err)
}
log('1. propose - reading proposal', msg)
try {
selection(session, nonceOut, proposeIn)
crypto.identify(state, msg)
crypto.selectProtocols(state)
} catch (err) {

@@ -59,89 +37,1 @@ return cb(err)

}
// Generate and send Hello packet.
// Hello = (rand, PublicKey, Supported)
function makeProposal (session, nonceOut) {
session.local.permanentPubKey = session.localKey.public
const myPubKeyBytes = session.local.permanentPubKey.bytes
return pbm.Propose.encode({
rand: nonceOut,
pubkey: myPubKeyBytes,
exchanges: support.exchanges.join(','),
ciphers: support.ciphers.join(','),
hashes: support.hashes.join(',')
})
}
function readProposal (bytes) {
return pbm.Propose.decode(bytes)
}
function identify (session, proposeIn) {
log('1.1 identify')
session.remote.permanentPubKey = crypto.unmarshalPublicKey(proposeIn.pubkey)
session.remotePeer = PeerId.createFromPubKey(proposeIn.pubkey.toString('base64'))
log('1.1 identify - %s - identified remote peer as %s', session.localPeer.toB58String(), session.remotePeer.toB58String())
}
function selection (session, nonceOut, proposeIn) {
log('1.2 selection')
const local = {
pubKeyBytes: session.local.permanentPubKey.bytes,
exchanges: support.exchanges,
hashes: support.hashes,
ciphers: support.ciphers,
nonce: nonceOut
}
const remote = {
pubKeyBytes: proposeIn.pubkey,
exchanges: proposeIn.exchanges.split(','),
hashes: proposeIn.hashes.split(','),
ciphers: proposeIn.ciphers.split(','),
nonce: proposeIn.rand
}
let selected = selectBest(local, remote)
session.proposal.order = selected.order
session.local.curveT = selected.curveT
session.local.cipherT = selected.cipherT
session.local.hashT = selected.hashT
// we use the same params for both directions (must choose same curve)
// WARNING: if they dont SelectBest the same way, this won't work...
session.remote.curveT = session.local.curveT
session.remote.cipherT = session.local.cipherT
session.remote.hashT = session.local.hashT
}
function selectBest (local, remote) {
const oh1 = digest(Buffer.concat([
remote.pubKeyBytes,
local.nonce
]))
const oh2 = digest(Buffer.concat([
local.pubKeyBytes,
remote.nonce
]))
const order = Buffer.compare(oh1, oh2)
if (order === 0) {
throw new Error('you are trying to talk to yourself')
}
return {
curveT: support.theBest(order, local.exchanges, remote.exchanges),
cipherT: support.theBest(order, local.ciphers, remote.ciphers),
hashT: support.theBest(order, local.hashes, remote.hashes),
order
}
}
function digest (buf) {
return mh.digest(buf, 'sha2-256', buf.length)
}
'use strict'
const duplexify = require('duplexify')
const lpstream = require('length-prefixed-stream')
const PassThrough = require('readable-stream').PassThrough
const pull = require('pull-stream')
const Connection = require('interface-connection').Connection
const handshake = require('./handshake')
const State = require('./state')
exports.SecureSession = class SecureSession {
constructor (local, key, insecure) {
this.localKey = key
this.localPeer = local
this.sharedSecret = null
this.local = {}
this.remote = {}
this.proposal = {}
this.insecure = insecure
this.secure = null
const e = lpstream.encode()
const d = lpstream.decode()
this.insecureLp = duplexify(e, d)
e.pipe(this.insecure)
this.insecure.pipe(d)
if (!this.localPeer) {
if (!local) {
throw new Error('no local id provided')
}
if (!this.localKey) {
if (!key) {
throw new Error('no local private key provided')
}
// Enable when implemented in js-peer-id
// if (!this.localPeer.matchesPrivateKey(this.localKey)) {
// throw new Error('peer.ID does not match privateKey')
// }
if (!insecure) {
throw new Error('no insecure stream provided')
}
}
secureStream () {
let handshaked = false
const reader = new PassThrough()
const writer = new PassThrough()
const dp = duplexify(writer, reader)
const originalRead = reader.read.bind(reader)
const originalWrite = writer.write.bind(writer)
this.state = new State(local, key)
this.insecure = insecure
const doHandshake = () => {
if (handshaked) return
handshaked = true
// Restore methods to avoid overhead
reader.read = originalRead
writer.write = originalWrite
this.handshake((err) => {
if (err) {
dp.emit('error', err)
}
// Pipe things together
writer.pipe(this.secure)
this.secure.pipe(reader)
dp.uncork()
dp.resume()
})
}
// patch to detect first read
reader.read = (size) => {
doHandshake()
originalRead(size)
}
// patch to detect first write
writer.write = (chunk, encoding, callback) => {
doHandshake()
originalWrite(chunk, encoding, callback)
}
dp.cork()
dp.pause()
return dp
pull(
this.insecure,
handshake(this.state),
this.insecure
)
}
handshake (cb) {
// TODO: figure out how to best handle the handshake timeout
if (this._handshakeLock) {
return cb(new Error('handshake already in progress'))
}
this._handshakeLock = true
const finish = (err) => {
this._handshakeLock = false
cb(err)
}
if (this._handshakeDone) {
return finish()
}
handshake(this, (err) => {
if (err) {
return finish(err)
}
this._handshakeDone = true
finish()
})
get secure () {
return new Connection(this.state.secure, this.insecure)
}
}
'use strict'
const mh = require('multihashing')
const forge = require('node-forge')
const lp = require('pull-length-prefixed')
const pull = require('pull-stream')

@@ -89,1 +92,53 @@ exports.exchanges = [

}
exports.randomBytes = (nonceSize) => {
return new Buffer(forge.random.getBytesSync(nonceSize), 'binary')
}
exports.selectBest = (local, remote) => {
const oh1 = exports.digest(Buffer.concat([
remote.pubKeyBytes,
local.nonce
]))
const oh2 = exports.digest(Buffer.concat([
local.pubKeyBytes,
remote.nonce
]))
const order = Buffer.compare(oh1, oh2)
if (order === 0) {
throw new Error('you are trying to talk to yourself')
}
return {
curveT: exports.theBest(order, local.exchanges, remote.exchanges),
cipherT: exports.theBest(order, local.ciphers, remote.ciphers),
hashT: exports.theBest(order, local.hashes, remote.hashes),
order
}
}
exports.digest = (buf) => {
return mh.digest(buf, 'sha2-256', buf.length)
}
exports.write = function write (state, msg, cb) {
cb = cb || (() => {})
pull(
pull.values([
msg
]),
lp.encode({fixed: true, bytes: 4}),
pull.collect((err, res) => {
if (err) {
return cb(err)
}
state.shake.write(res[0])
cb()
})
)
}
exports.read = function read (reader, cb) {
lp.decodeFromReader(reader, {fixed: true, bytes: 4}, cb)
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc