mirror of
https://github.com/fluencelabs/js-libp2p-secio
synced 2025-03-15 09:50:55 +00:00
Async Crypto Endeavour (+benchmarks!) (#19)
* feat: add benchmarks and integrate with new libp2p-crypto * start removing node-forge * less forge * cleanup and fixes * benchmarked and tested * remove unused .aegir * Replace lib multihashing with multihashing-async * cleanup package.json * Using string instead of buffer for proto file * chore: fix benchmarks * docs(readme): update README URLs based on HTTP redirects * ready for next aegir * update aegir * ready * chore: update deps
This commit is contained in:
parent
4f1911f817
commit
a1167c0084
16
.aegir.js
16
.aegir.js
@ -1,16 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
webpack: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'node-forge': path.resolve(
|
||||
path.dirname(require.resolve('libp2p-crypto')),
|
||||
'../vendor/forge.bundle.js'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -31,5 +31,4 @@ build
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
|
||||
lib
|
||||
dist
|
||||
|
20
.travis.yml
20
.travis.yml
@ -1,9 +1,15 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- 4
|
||||
- 5
|
||||
- stable
|
||||
matrix:
|
||||
include:
|
||||
- node_js: 4
|
||||
env: CXX=g++-4.8
|
||||
- node_js: 6
|
||||
env:
|
||||
- SAUCE=true
|
||||
- CXX=g++-4.8
|
||||
- node_js: stable
|
||||
env: CXX=g++-4.8
|
||||
|
||||
# Make sure we have new NPM.
|
||||
before_install:
|
||||
@ -14,7 +20,6 @@ script:
|
||||
- npm test
|
||||
- npm run coverage
|
||||
|
||||
|
||||
before_script:
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
@ -22,13 +27,10 @@ before_script:
|
||||
after_success:
|
||||
- npm run coverage-publish
|
||||
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
|
||||
addons:
|
||||
firefox: 'latest'
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- g++-4.8
|
||||
- g++-4.8
|
@ -8,6 +8,10 @@
|
||||
[](https://travis-ci.org/libp2p/js-libp2p-secio)
|
||||
[](https://circleci.com/gh/libp2p/js-libp2p-secio)
|
||||
[](https://david-dm.org/libp2p/js-libp2p-secio) [](https://github.com/feross/standard)
|
||||

|
||||

|
||||
|
||||
[](https://saucelabs.com/u/libp2p-js-secio)
|
||||
|
||||
> Secio implementation in JavaScript
|
||||
|
||||
|
87
benchmarks/send.js
Normal file
87
benchmarks/send.js
Normal file
@ -0,0 +1,87 @@
|
||||
'use strict'
|
||||
|
||||
const Benchmark = require('benchmark')
|
||||
const pull = require('pull-stream')
|
||||
const pair = require('pull-pair/duplex')
|
||||
const PeerId = require('peer-id')
|
||||
const crypto = require('libp2p-crypto')
|
||||
|
||||
const secio = require('../src')
|
||||
|
||||
const suite = new Benchmark.Suite('secio')
|
||||
const ids = []
|
||||
|
||||
suite.add('createKey', function (d) {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, key) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
PeerId.createFromPrivKey(key.bytes, (err, id) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
ids.push(id)
|
||||
d.resolve()
|
||||
})
|
||||
})
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
.add('send', function (deferred) {
|
||||
const p = pair()
|
||||
|
||||
createSession(p[0], (err, local) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
createSession(p[1], (err, remote) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
sendMessages(local, remote)
|
||||
})
|
||||
})
|
||||
|
||||
function sendMessages (local, remote) {
|
||||
pull(
|
||||
pull.infinite(),
|
||||
pull.take(100),
|
||||
pull.map((val) => Buffer(val.toString())),
|
||||
local
|
||||
)
|
||||
|
||||
pull(
|
||||
remote,
|
||||
pull.take(100),
|
||||
pull.collect((err, chunks) => {
|
||||
if (err) throw err
|
||||
if (chunks.length !== 100) throw new Error('Did not receive enough chunks')
|
||||
deferred.resolve()
|
||||
})
|
||||
)
|
||||
}
|
||||
}, {
|
||||
defer: true
|
||||
})
|
||||
.on('cycle', (event) => {
|
||||
console.log(String(event.target))
|
||||
})
|
||||
// run async
|
||||
.run({
|
||||
async: true
|
||||
})
|
||||
|
||||
function createSession (insecure, cb) {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, key) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
PeerId.createFromPrivKey(key.bytes, (err, id) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
cb(null, secio.encrypt(id, key, insecure))
|
||||
})
|
||||
})
|
||||
}
|
33
package.json
33
package.json
@ -2,8 +2,7 @@
|
||||
"name": "libp2p-secio",
|
||||
"version": "0.5.0",
|
||||
"description": "Secio implementation in JavaScript",
|
||||
"main": "lib/index.js",
|
||||
"jsnext:main": "src/index.js",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"lint": "aegir-lint",
|
||||
"build": "aegir-build",
|
||||
@ -14,7 +13,8 @@
|
||||
"release-minor": "aegir-release --type minor",
|
||||
"release-major": "aegir-release --type major",
|
||||
"coverage": "aegir-coverage",
|
||||
"coverage-publish": "aegir-coverage publish"
|
||||
"coverage-publish": "aegir-coverage publish",
|
||||
"bench": "node benchmarks/send.js"
|
||||
},
|
||||
"keywords": [
|
||||
"IPFS",
|
||||
@ -22,37 +22,36 @@
|
||||
"crypto",
|
||||
"rsa"
|
||||
],
|
||||
"author": "Friedel Ziegelmayer <dignifiedqurie@gmail.com>",
|
||||
"author": "Friedel Ziegelmayer <dignifiedquire@gmail.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"async": "^2.1.2",
|
||||
"debug": "^2.2.0",
|
||||
"interface-connection": "^0.2.1",
|
||||
"libp2p-crypto": "^0.6.1",
|
||||
"multihashing": "^0.2.1",
|
||||
"node-forge": "^0.6.42",
|
||||
"peer-id": "^0.7.0",
|
||||
"interface-connection": "^0.3.0",
|
||||
"libp2p-crypto": "^0.7.0",
|
||||
"multihashing-async": "^0.2.0",
|
||||
"peer-id": "^0.8.0",
|
||||
"protocol-buffers": "^3.1.6",
|
||||
"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"
|
||||
"pull-stream": "^3.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^8.0.0",
|
||||
"aegir": "^9.0.1",
|
||||
"benchmark": "^2.1.2",
|
||||
"chai": "^3.5.0",
|
||||
"multistream-select": "^0.11.0",
|
||||
"multistream-select": "^0.11.1",
|
||||
"pre-commit": "^1.1.3",
|
||||
"pull-pair": "^1.1.0",
|
||||
"run-parallel": "^1.1.6"
|
||||
"pull-pushable": "^2.0.1"
|
||||
},
|
||||
"pre-commit": [
|
||||
"lint",
|
||||
"test"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^4.0.0"
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -68,4 +67,4 @@
|
||||
"Richard Littauer <richard.littauer@gmail.com>",
|
||||
"greenkeeperio-bot <support@greenkeeper.io>"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
98
src/etm.js
98
src/etm.js
@ -1,73 +1,67 @@
|
||||
'use strict'
|
||||
|
||||
const through = require('pull-through')
|
||||
const pull = require('pull-stream')
|
||||
const lp = require('pull-length-prefixed')
|
||||
|
||||
const toForgeBuffer = require('./support').toForgeBuffer
|
||||
|
||||
const lpOpts = {
|
||||
fixed: true,
|
||||
bytes: 4
|
||||
}
|
||||
|
||||
exports.createBoxStream = (cipher, mac) => {
|
||||
const pt = through(function (chunk) {
|
||||
cipher.update(toForgeBuffer(chunk))
|
||||
|
||||
if (cipher.output.length() > 0) {
|
||||
const data = new Buffer(cipher.output.getBytes(), 'binary')
|
||||
mac.update(data.toString('binary'))
|
||||
const macBuffer = new Buffer(mac.digest().getBytes(), 'binary')
|
||||
|
||||
this.queue(Buffer.concat([data, macBuffer]))
|
||||
// reset hmac
|
||||
mac.start(null, null)
|
||||
}
|
||||
})
|
||||
|
||||
return pull(
|
||||
pt,
|
||||
pull.asyncMap((chunk, cb) => {
|
||||
cipher.encrypt(chunk, (err, data) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
mac.digest(data, (err, digest) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
cb(null, Buffer.concat([data, digest]))
|
||||
})
|
||||
})
|
||||
}),
|
||||
lp.encode(lpOpts)
|
||||
)
|
||||
}
|
||||
|
||||
exports.createUnboxStream = (decipher, mac) => {
|
||||
const pt = through(function (chunk) {
|
||||
const l = chunk.length
|
||||
const macSize = mac.getMac().length()
|
||||
|
||||
if (l < macSize) {
|
||||
return this.emit('error', new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
|
||||
}
|
||||
|
||||
const mark = l - macSize
|
||||
const data = chunk.slice(0, mark)
|
||||
const macd = chunk.slice(mark)
|
||||
|
||||
// Clear out any previous data
|
||||
mac.start(null, null)
|
||||
|
||||
mac.update(data.toString('binary'))
|
||||
const expected = new Buffer(mac.getMac().getBytes(), 'binary')
|
||||
|
||||
// reset hmac
|
||||
mac.start(null, null)
|
||||
if (!macd.equals(expected)) {
|
||||
return this.emit('error', new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
|
||||
}
|
||||
|
||||
// all good, decrypt
|
||||
decipher.update(toForgeBuffer(data))
|
||||
|
||||
if (decipher.output.length() > 0) {
|
||||
const data = new Buffer(decipher.output.getBytes(), 'binary')
|
||||
this.queue(data)
|
||||
}
|
||||
})
|
||||
|
||||
return pull(
|
||||
lp.decode(lpOpts),
|
||||
pt
|
||||
pull.asyncMap((chunk, cb) => {
|
||||
const l = chunk.length
|
||||
const macSize = mac.length
|
||||
|
||||
if (l < macSize) {
|
||||
return cb(new Error(`buffer (${l}) shorter than MAC size (${macSize})`))
|
||||
}
|
||||
|
||||
const mark = l - macSize
|
||||
const data = chunk.slice(0, mark)
|
||||
const macd = chunk.slice(mark)
|
||||
|
||||
mac.digest(data, (err, expected) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
if (!macd.equals(expected)) {
|
||||
return cb(new Error(`MAC Invalid: ${macd.toString('hex')} != ${expected.toString('hex')}`))
|
||||
}
|
||||
|
||||
// all good, decrypt
|
||||
decipher.decrypt(data, (err, decrypted) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
cb(null, decrypted)
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
|
||||
const protobuf = require('protocol-buffers')
|
||||
const path = require('path')
|
||||
const fs = require('fs')
|
||||
const PeerId = require('peer-id')
|
||||
const crypto = require('libp2p-crypto')
|
||||
const parallel = require('async/parallel')
|
||||
const waterfall = require('async/waterfall')
|
||||
const debug = require('debug')
|
||||
const log = debug('libp2p:secio')
|
||||
log.error = debug('libp2p:secio:error')
|
||||
|
||||
const pbm = protobuf(fs.readFileSync(path.join(__dirname, 'secio.proto')))
|
||||
const pbm = protobuf(require('./secio.proto'))
|
||||
|
||||
const support = require('../support')
|
||||
|
||||
@ -29,27 +29,38 @@ exports.createProposal = (state) => {
|
||||
return state.proposalEncoded.out
|
||||
}
|
||||
|
||||
exports.createExchange = (state) => {
|
||||
const res = crypto.generateEphemeralKeyPair(state.protocols.local.curveT)
|
||||
state.ephemeralKey.local = res.key
|
||||
state.shared.generate = res.genSharedKey
|
||||
exports.createExchange = (state, callback) => {
|
||||
crypto.generateEphemeralKeyPair(state.protocols.local.curveT, (err, res) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
// Gather corpus to sign.
|
||||
const selectionOut = Buffer.concat([
|
||||
state.proposalEncoded.out,
|
||||
state.proposalEncoded.in,
|
||||
state.ephemeralKey.local
|
||||
])
|
||||
state.ephemeralKey.local = res.key
|
||||
state.shared.generate = res.genSharedKey
|
||||
|
||||
state.exchange.out = {
|
||||
epubkey: state.ephemeralKey.local,
|
||||
signature: new Buffer(state.key.local.sign(selectionOut), 'binary')
|
||||
}
|
||||
// Gather corpus to sign.
|
||||
const selectionOut = Buffer.concat([
|
||||
state.proposalEncoded.out,
|
||||
state.proposalEncoded.in,
|
||||
state.ephemeralKey.local
|
||||
])
|
||||
|
||||
return pbm.Exchange.encode(state.exchange.out)
|
||||
state.key.local.sign(selectionOut, (err, sig) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
state.exchange.out = {
|
||||
epubkey: state.ephemeralKey.local,
|
||||
signature: sig
|
||||
}
|
||||
|
||||
callback(null, pbm.Exchange.encode(state.exchange.out))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.identify = (state, msg) => {
|
||||
exports.identify = (state, msg, callback) => {
|
||||
log('1.1 identify')
|
||||
|
||||
state.proposalEncoded.in = msg
|
||||
@ -57,12 +68,19 @@ exports.identify = (state, msg) => {
|
||||
const pubkey = state.proposal.in.pubkey
|
||||
|
||||
state.key.remote = crypto.unmarshalPublicKey(pubkey)
|
||||
state.id.remote = PeerId.createFromPubKey(pubkey.toString('base64'))
|
||||
PeerId.createFromPubKey(pubkey.toString('base64'), (err, remoteId) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
|
||||
state.id.remote = remoteId
|
||||
|
||||
log('1.1 identify - %s - identified remote peer as %s', state.id.local.toB58String(), state.id.remote.toB58String())
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
exports.selectProtocols = (state) => {
|
||||
exports.selectProtocols = (state, callback) => {
|
||||
log('1.2 selection')
|
||||
|
||||
const local = {
|
||||
@ -81,25 +99,30 @@ exports.selectProtocols = (state) => {
|
||||
nonce: state.proposal.in.rand
|
||||
}
|
||||
|
||||
let selected = support.selectBest(local, remote)
|
||||
// we use the same params for both directions (must choose same curve)
|
||||
// WARNING: if they dont SelectBest the same way, this won't work...
|
||||
state.protocols.remote = {
|
||||
order: selected.order,
|
||||
curveT: selected.curveT,
|
||||
cipherT: selected.cipherT,
|
||||
hashT: selected.hashT
|
||||
}
|
||||
support.selectBest(local, remote, (err, selected) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
// we use the same params for both directions (must choose same curve)
|
||||
// WARNING: if they dont SelectBest the same way, this won't work...
|
||||
state.protocols.remote = {
|
||||
order: selected.order,
|
||||
curveT: selected.curveT,
|
||||
cipherT: selected.cipherT,
|
||||
hashT: selected.hashT
|
||||
}
|
||||
|
||||
state.protocols.local = {
|
||||
order: selected.order,
|
||||
curveT: selected.curveT,
|
||||
cipherT: selected.cipherT,
|
||||
hashT: selected.hashT
|
||||
}
|
||||
state.protocols.local = {
|
||||
order: selected.order,
|
||||
curveT: selected.curveT,
|
||||
cipherT: selected.cipherT,
|
||||
hashT: selected.hashT
|
||||
}
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
exports.verify = (state, msg) => {
|
||||
exports.verify = (state, msg, callback) => {
|
||||
log('2.1. verify')
|
||||
|
||||
state.exchange.in = pbm.Exchange.decode(msg)
|
||||
@ -111,43 +134,57 @@ exports.verify = (state, msg) => {
|
||||
state.ephemeralKey.remote
|
||||
])
|
||||
|
||||
const sigOk = state.key.remote.verify(selectionIn, state.exchange.in.signature)
|
||||
state.key.remote.verify(selectionIn, state.exchange.in.signature, (err, sigOk) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
if (!sigOk) {
|
||||
throw new Error('Bad signature')
|
||||
}
|
||||
if (!sigOk) {
|
||||
return callback(new Error('Bad signature'))
|
||||
}
|
||||
|
||||
log('2.1. verify - signature verified')
|
||||
log('2.1. verify - signature verified')
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
exports.generateKeys = (state) => {
|
||||
exports.generateKeys = (state, callback) => {
|
||||
log('2.2. keys')
|
||||
|
||||
state.shared.secret = state.shared.generate(state.exchange.in.epubkey)
|
||||
waterfall([
|
||||
(cb) => state.shared.generate(state.exchange.in.epubkey, cb),
|
||||
(secret, cb) => {
|
||||
state.shared.secret = secret
|
||||
|
||||
const keys = crypto.keyStretcher(
|
||||
state.protocols.local.cipherT,
|
||||
state.protocols.local.hashT,
|
||||
state.shared.secret
|
||||
)
|
||||
crypto.keyStretcher(
|
||||
state.protocols.local.cipherT,
|
||||
state.protocols.local.hashT,
|
||||
state.shared.secret,
|
||||
cb
|
||||
)
|
||||
},
|
||||
(keys, cb) => {
|
||||
// use random nonces to decide order.
|
||||
if (state.protocols.local.order > 0) {
|
||||
state.protocols.local.keys = keys.k1
|
||||
state.protocols.remote.keys = keys.k2
|
||||
} else if (state.protocols.local.order < 0) {
|
||||
// swap
|
||||
state.protocols.local.keys = keys.k2
|
||||
state.protocols.remote.keys = keys.k1
|
||||
} else {
|
||||
// we should've bailed before state. but if not, bail here.
|
||||
return cb(new Error('you are trying to talk to yourself'))
|
||||
}
|
||||
|
||||
// use random nonces to decide order.
|
||||
if (state.protocols.local.order > 0) {
|
||||
state.protocols.local.keys = keys.k1
|
||||
state.protocols.remote.keys = keys.k2
|
||||
} else if (state.protocols.local.order < 0) {
|
||||
// swap
|
||||
state.protocols.local.keys = keys.k2
|
||||
state.protocols.remote.keys = keys.k1
|
||||
} else {
|
||||
// we should've bailed before state. but if not, bail here.
|
||||
throw new Error('you are trying to talk to yourself')
|
||||
}
|
||||
log('2.3. mac + cipher')
|
||||
|
||||
log('2.3. mac + cipher')
|
||||
|
||||
support.makeMacAndCipher(state.protocols.local)
|
||||
support.makeMacAndCipher(state.protocols.remote)
|
||||
parallel([
|
||||
(cb) => support.makeMacAndCipher(state.protocols.local, cb),
|
||||
(cb) => support.makeMacAndCipher(state.protocols.remote, cb)
|
||||
], cb)
|
||||
}
|
||||
], callback)
|
||||
}
|
||||
|
||||
exports.verifyNonce = (state, n2) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')
|
||||
const waterfall = require('async/waterfall')
|
||||
|
||||
const support = require('../support')
|
||||
const crypto = require('./crypto')
|
||||
@ -14,21 +15,22 @@ module.exports = function exchange (state, cb) {
|
||||
log('2. exchange - start')
|
||||
|
||||
log('2. exchange - writing exchange')
|
||||
support.write(state, crypto.createExchange(state))
|
||||
support.read(state.shake, (err, msg) => {
|
||||
waterfall([
|
||||
(cb) => crypto.createExchange(state, cb),
|
||||
(ex, cb) => {
|
||||
support.write(state, ex)
|
||||
support.read(state.shake, cb)
|
||||
},
|
||||
(msg, cb) => {
|
||||
log('2. exchange - reading exchange')
|
||||
crypto.verify(state, msg, cb)
|
||||
},
|
||||
(cb) => crypto.generateKeys(state, cb)
|
||||
], (err) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
log('2. exchange - reading exchange')
|
||||
|
||||
try {
|
||||
crypto.verify(state, msg)
|
||||
crypto.generateKeys(state)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
log('2. exchange - finish')
|
||||
cb()
|
||||
})
|
||||
|
@ -17,7 +17,11 @@ module.exports = function finish (state, cb) {
|
||||
|
||||
const proto = state.protocols
|
||||
const stream = state.shake.rest()
|
||||
const shake = handshake({timeout: state.timeout})
|
||||
const shake = handshake({timeout: state.timeout}, (err) => {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
pull(
|
||||
stream,
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const series = require('run-series')
|
||||
const series = require('async/series')
|
||||
|
||||
const propose = require('./propose')
|
||||
const exchange = require('./exchange')
|
||||
|
@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const debug = require('debug')
|
||||
const waterfall = require('async/waterfall')
|
||||
|
||||
const support = require('../support')
|
||||
const crypto = require('./crypto')
|
||||
@ -15,22 +16,20 @@ module.exports = function propose (state, cb) {
|
||||
|
||||
log('1. propose - writing proposal')
|
||||
support.write(state, crypto.createProposal(state))
|
||||
support.read(state.shake, (err, msg) => {
|
||||
|
||||
waterfall([
|
||||
(cb) => support.read(state.shake, cb),
|
||||
(msg, cb) => {
|
||||
log('1. propose - reading proposal', msg)
|
||||
crypto.identify(state, msg, cb)
|
||||
},
|
||||
(cb) => crypto.selectProtocols(state, cb)
|
||||
], (err) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
log('1. propose - reading proposal', msg)
|
||||
|
||||
try {
|
||||
crypto.identify(state, msg)
|
||||
crypto.selectProtocols(state)
|
||||
} catch (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
log('1. propose - finish')
|
||||
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
message Propose {
|
||||
optional bytes rand = 1;
|
||||
optional bytes pubkey = 2;
|
||||
optional string exchanges = 3;
|
||||
optional string ciphers = 4;
|
||||
optional string hashes = 5;
|
||||
}
|
||||
|
||||
message Exchange {
|
||||
optional bytes epubkey = 1;
|
||||
optional bytes signature = 2;
|
||||
}
|
14
src/handshake/secio.proto.js
Normal file
14
src/handshake/secio.proto.js
Normal file
@ -0,0 +1,14 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = `message Propose {
|
||||
optional bytes rand = 1;
|
||||
optional bytes pubkey = 2;
|
||||
optional string exchanges = 3;
|
||||
optional string ciphers = 4;
|
||||
optional string hashes = 5;
|
||||
}
|
||||
|
||||
message Exchange {
|
||||
optional bytes epubkey = 1;
|
||||
optional bytes signature = 2;
|
||||
}`
|
10
src/index.js
10
src/index.js
@ -8,7 +8,7 @@ const State = require('./state')
|
||||
|
||||
module.exports = {
|
||||
tag: '/secio/1.0.0',
|
||||
encrypt (local, key, insecure) {
|
||||
encrypt (local, key, insecure, callback) {
|
||||
if (!local) {
|
||||
throw new Error('no local id provided')
|
||||
}
|
||||
@ -21,7 +21,13 @@ module.exports = {
|
||||
throw new Error('no insecure stream provided')
|
||||
}
|
||||
|
||||
const state = new State(local, key)
|
||||
if (!callback) {
|
||||
callback = (err) => {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const state = new State(local, key, callback)
|
||||
|
||||
pull(
|
||||
insecure,
|
||||
|
@ -5,6 +5,11 @@ const deferred = require('pull-defer')
|
||||
|
||||
class State {
|
||||
constructor (id, key, timeout, cb) {
|
||||
if (typeof timeout === 'function') {
|
||||
cb = timeout
|
||||
timeout = undefined
|
||||
}
|
||||
|
||||
this.setup()
|
||||
this.id.local = id
|
||||
this.key.local = key
|
||||
|
108
src/support.js
108
src/support.js
@ -1,9 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
const mh = require('multihashing')
|
||||
const forge = require('node-forge')
|
||||
const mh = require('multihashing-async')
|
||||
const lp = require('pull-length-prefixed')
|
||||
const pull = require('pull-stream')
|
||||
const crypto = require('libp2p-crypto')
|
||||
const parallel = require('async/parallel')
|
||||
|
||||
exports.exchanges = [
|
||||
'P-256',
|
||||
@ -47,78 +48,73 @@ exports.theBest = (order, p1, p2) => {
|
||||
throw new Error('No algorithms in common!')
|
||||
}
|
||||
|
||||
exports.makeMacAndCipher = (target) => {
|
||||
target.mac = makeMac(target.hashT, target.keys.macKey)
|
||||
target.cipher = makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey)
|
||||
exports.makeMacAndCipher = (target, callback) => {
|
||||
parallel([
|
||||
(cb) => makeMac(target.hashT, target.keys.macKey, cb),
|
||||
(cb) => makeCipher(target.cipherT, target.keys.iv, target.keys.cipherKey, cb)
|
||||
], (err, macAndCipher) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
target.mac = macAndCipher[0]
|
||||
target.cipher = macAndCipher[1]
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
const hashMap = {
|
||||
SHA1: 'sha1',
|
||||
SHA256: 'sha256',
|
||||
// workaround for https://github.com/digitalbazaar/forge/issues/401
|
||||
SHA512: forge.md.sha512.create()
|
||||
function makeMac (hash, key, callback) {
|
||||
crypto.hmac.create(hash, key, callback)
|
||||
}
|
||||
|
||||
const toForgeBuffer = exports.toForgeBuffer = (buf) => (
|
||||
forge.util.createBuffer(buf.toString('binary'))
|
||||
)
|
||||
|
||||
function makeMac (hashType, key) {
|
||||
const hash = hashMap[hashType]
|
||||
|
||||
if (!hash) {
|
||||
throw new Error(`unsupported hash type: ${hashType}`)
|
||||
}
|
||||
|
||||
const mac = forge.hmac.create()
|
||||
mac.start(hash, toForgeBuffer(key))
|
||||
return mac
|
||||
}
|
||||
|
||||
function makeCipher (cipherType, iv, key) {
|
||||
function makeCipher (cipherType, iv, key, callback) {
|
||||
if (cipherType === 'AES-128' || cipherType === 'AES-256') {
|
||||
// aes in counter (CTR) mode because that is what
|
||||
// is used in go (cipher.NewCTR)
|
||||
const cipher = forge.cipher.createCipher('AES-CTR', toForgeBuffer(key))
|
||||
cipher.start({iv: toForgeBuffer(iv)})
|
||||
return cipher
|
||||
return crypto.aes.create(key, iv, callback)
|
||||
}
|
||||
|
||||
// TODO: Blowfish is not supported in node-forge, figure out if
|
||||
// it's needed and if so find a library for it.
|
||||
|
||||
throw new Error(`unrecognized cipher type: ${cipherType}`)
|
||||
// TODO: figure out if Blowfish is needed and if so find a library for it.
|
||||
callback(new Error(`unrecognized cipher type: ${cipherType}`))
|
||||
}
|
||||
|
||||
exports.randomBytes = (nonceSize) => {
|
||||
return new Buffer(forge.random.getBytesSync(nonceSize), 'binary')
|
||||
return Buffer.from(crypto.webcrypto.getRandomValues(new Uint8Array(nonceSize)))
|
||||
}
|
||||
|
||||
exports.selectBest = (local, remote) => {
|
||||
const oh1 = exports.digest(Buffer.concat([
|
||||
exports.selectBest = (local, remote, cb) => {
|
||||
exports.digest(Buffer.concat([
|
||||
remote.pubKeyBytes,
|
||||
local.nonce
|
||||
]))
|
||||
const oh2 = exports.digest(Buffer.concat([
|
||||
local.pubKeyBytes,
|
||||
remote.nonce
|
||||
]))
|
||||
const order = Buffer.compare(oh1, oh2)
|
||||
]), (err, oh1) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
if (order === 0) {
|
||||
throw new Error('you are trying to talk to yourself')
|
||||
}
|
||||
exports.digest(Buffer.concat([
|
||||
local.pubKeyBytes,
|
||||
remote.nonce
|
||||
]), (err, oh2) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
const order = Buffer.compare(oh1, oh2)
|
||||
|
||||
if (order === 0) {
|
||||
cb(new Error('you are trying to talk to yourself'))
|
||||
}
|
||||
|
||||
cb(null, {
|
||||
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.digest = (buf, cb) => {
|
||||
mh.digest(buf, 'sha2-256', buf.length, cb)
|
||||
}
|
||||
|
||||
exports.write = function write (state, msg, cb) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint max-nested-callbacks: ["error", 8] */
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
@ -5,8 +6,8 @@ const pair = require('pull-pair/duplex')
|
||||
const expect = require('chai').expect
|
||||
const PeerId = require('peer-id')
|
||||
const crypto = require('libp2p-crypto')
|
||||
const parallel = require('run-parallel')
|
||||
const series = require('run-series')
|
||||
const parallel = require('async/parallel')
|
||||
const series = require('async/series')
|
||||
const ms = require('multistream-select')
|
||||
const pull = require('pull-stream')
|
||||
const Listener = ms.Listener
|
||||
@ -21,23 +22,31 @@ describe('libp2p-secio', () => {
|
||||
|
||||
it('upgrades a connection', (done) => {
|
||||
const p = pair()
|
||||
createSession(p[0], (err, local) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
const local = createSession(p[0])
|
||||
const remote = createSession(p[1])
|
||||
createSession(p[1], (err, remote) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
|
||||
pull(
|
||||
pull.values(['hello world']),
|
||||
local
|
||||
)
|
||||
pull(
|
||||
pull.values([new Buffer('hello world')]),
|
||||
local
|
||||
)
|
||||
|
||||
pull(
|
||||
remote,
|
||||
pull.collect((err, chunks) => {
|
||||
expect(err).to.not.exist
|
||||
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||
done()
|
||||
pull(
|
||||
remote,
|
||||
pull.collect((err, chunks) => {
|
||||
expect(err).to.not.exist
|
||||
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('works over multistream', (done) => {
|
||||
@ -45,8 +54,7 @@ describe('libp2p-secio', () => {
|
||||
|
||||
const listener = new Listener()
|
||||
const dialer = new Dialer()
|
||||
let local
|
||||
let remote
|
||||
|
||||
series([
|
||||
(cb) => parallel([
|
||||
(cb) => listener.handle(p[0], cb),
|
||||
@ -54,35 +62,58 @@ describe('libp2p-secio', () => {
|
||||
], cb),
|
||||
(cb) => {
|
||||
listener.addHandler('/banana/1.0.0', (conn) => {
|
||||
local = createSession(conn)
|
||||
pull(
|
||||
local,
|
||||
pull.collect((err, chunks) => {
|
||||
expect(err).to.not.exist
|
||||
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||
done()
|
||||
})
|
||||
)
|
||||
createSession(conn, (err, local) => {
|
||||
if (err) {
|
||||
return done(err)
|
||||
}
|
||||
pull(
|
||||
local,
|
||||
pull.collect((err, chunks) => {
|
||||
expect(err).to.not.exist
|
||||
expect(chunks).to.be.eql([new Buffer('hello world')])
|
||||
done()
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
cb()
|
||||
},
|
||||
(cb) => dialer.select('/banana/1.0.0', (err, conn) => {
|
||||
remote = createSession(conn)
|
||||
pull(
|
||||
pull.values(['hello world']),
|
||||
remote
|
||||
)
|
||||
cb(err)
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
createSession(conn, (err, remote) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
pull(
|
||||
pull.values([new Buffer('hello world')]),
|
||||
remote
|
||||
)
|
||||
cb()
|
||||
})
|
||||
})
|
||||
], (err) => {
|
||||
if (err) throw err
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function createSession (insecure) {
|
||||
const key = crypto.generateKeyPair('RSA', 2048)
|
||||
const id = PeerId.createFromPrivKey(key.bytes)
|
||||
function createSession (insecure, cb) {
|
||||
crypto.generateKeyPair('RSA', 2048, (err, key) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
return secio.encrypt(id, key, insecure)
|
||||
key.public.hash((err, digest) => {
|
||||
if (err) {
|
||||
return cb(err)
|
||||
}
|
||||
|
||||
cb(null, secio.encrypt(new PeerId(digest, key), key, insecure))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user