libp2p-secio
Advanced tools
Comparing version 0.3.1 to 0.4.2
'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); | ||
} | ||
}; |
111
lib/index.js
@@ -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) | ||
} |
111
src/index.js
'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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 2 instances in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6
31
87
5461193
13
64763
2
46
8
+ Addedinterface-connection@^0.2.1
+ Addedpull-defer@^0.2.2
+ Addedpull-handshake@^1.1.4
+ Addedpull-length-prefixed@^1.2.0
+ Addedpull-stream@^3.4.3
+ Addedpull-through@^1.0.18
+ Addedinterface-connection@0.2.1(transitive)
+ Addedlooper@3.0.0(transitive)
+ Addedmultihashes@0.2.2(transitive)
+ Addedpeer-id@0.7.0(transitive)
+ Addedpull-cat@1.1.11(transitive)
+ Addedpull-defer@0.2.3(transitive)
+ Addedpull-handshake@1.1.4(transitive)
+ Addedpull-length-prefixed@1.3.3(transitive)
+ Addedpull-pair@1.1.0(transitive)
+ Addedpull-pushable@2.2.0(transitive)
+ Addedpull-reader@1.3.1(transitive)
+ Addedpull-stream@3.7.0(transitive)
+ Addedpull-through@1.0.18(transitive)
+ Addedtimed-tape@0.1.1(transitive)
- Removedasync-buffered-reader@^1.2.1
- Removedduplexify@^3.4.3
- Removedlength-prefixed-stream@^1.5.0
- Removedreadable-stream@1.1.13
- Removedthrough2@^2.0.1
- Removedasync-buffered-reader@1.2.1(transitive)
- Removedbuffer-alloc-unsafe@1.1.0(transitive)
- Removedduplexify@3.7.1(transitive)
- Removedend-of-stream@1.4.4(transitive)
- Removedisarray@0.0.1(transitive)
- Removedlength-prefixed-stream@1.6.0(transitive)
- Removedonce@1.4.0(transitive)
- Removedpeer-id@0.6.7(transitive)
- Removedreadable-stream@1.1.13(transitive)
- Removedstream-shift@1.0.3(transitive)
- Removedstring_decoder@0.10.31(transitive)
- Removedwrappy@1.0.2(transitive)
Updatednode-forge@^0.6.42
Updatedpeer-id@^0.7.0