Compare commits

..

7 Commits

Author SHA1 Message Date
Vasco Santos
71f9af6f87 fix: subsequent dial abort 0.28 2020-09-30 11:00:14 +02:00
Jacob Heun
0e18735b8c chore: release version v0.28.10 2020-08-05 19:07:08 +02:00
Jacob Heun
f68ff35625 chore: update contributors 2020-08-05 19:07:07 +02:00
Alex Potsides
8c56ec0d23 fix: allow certain keychain operations without a password (#726)
* fix: allow certain keychain operations without a password

Listing, removing, renaming etc keys do not require a password so
the user should not be required to provide one.

This means we don't have to prompt the user to create a password
when they aren't going to do any operations that require a password.

* fix: make keychain pass optional

* fix: support libp2p creation without keychain pass

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
2020-08-05 19:03:11 +02:00
Vasco Santos
fa5ee873e3 chore: update node and npm badges (#727) 2020-08-05 18:26:50 +02:00
Jacob Heun
51d7ca44c1 feat(keychain): add support for ed25519 and secp keys (#725)
* feat(keychain): add support for ed25519 and secp keys

* chore: bump crypto

* refactor: cleanup keychain usage
2020-08-05 18:19:10 +02:00
Jacob Heun
726a746479 fix(identify): make agentversion dynamic and add it to the peerstore (#724) 2020-08-04 18:39:05 +02:00
12 changed files with 187 additions and 43 deletions

View File

@@ -1,3 +1,19 @@
<a name="0.28.10"></a>
## [0.28.10](https://github.com/libp2p/js-libp2p/compare/v0.28.9...v0.28.10) (2020-08-05)
### Bug Fixes
* allow certain keychain operations without a password ([#726](https://github.com/libp2p/js-libp2p/issues/726)) ([8c56ec0](https://github.com/libp2p/js-libp2p/commit/8c56ec0))
* **identify:** make agentversion dynamic and add it to the peerstore ([#724](https://github.com/libp2p/js-libp2p/issues/724)) ([726a746](https://github.com/libp2p/js-libp2p/commit/726a746))
### Features
* **keychain:** add support for ed25519 and secp keys ([#725](https://github.com/libp2p/js-libp2p/issues/725)) ([51d7ca4](https://github.com/libp2p/js-libp2p/commit/51d7ca4))
<a name="0.28.9"></a>
## [0.28.9](https://github.com/libp2p/js-libp2p/compare/v0.28.8...v0.28.9) (2020-07-27)

View File

@@ -23,8 +23,8 @@
<a href="https://david-dm.org/libp2p/js-libp2p"><img src="https://david-dm.org/libp2p/js-libp2p.svg?style=flat-square" /></a>
<a href="https://github.com/feross/standard"><img src="https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square"></a>
<a href="https://github.com/RichardLitt/standard-readme"><img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/npm-%3E%3D6.0.0-orange.svg?style=flat-square" /></a>
<a href=""><img src="https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square" /></a>
<br>
</p>

View File

@@ -1,6 +1,6 @@
{
"name": "libp2p",
"version": "0.28.9",
"version": "0.28.10",
"description": "JavaScript implementation of libp2p, a modular peer to peer network stack",
"leadMaintainer": "Jacob Heun <jacobheun@gmail.com>",
"main": "src/index.js",
@@ -126,43 +126,44 @@
"Vasco Santos <vasco.santos@moxy.studio>",
"Alan Shaw <alan@tableflip.io>",
"Cayman <caymannava@gmail.com>",
"Alex Potsides <alex@achingbrain.net>",
"Pedro Teixeira <i@pgte.me>",
"Friedel Ziegelmayer <dignifiedquire@gmail.com>",
"Alex Potsides <alex@achingbrain.net>",
"Maciej Krüger <mkg20001@gmail.com>",
"Hugo Dias <mail@hugodias.me>",
"Volker Mische <volker.mische@gmail.com>",
"dirkmc <dirkmdev@gmail.com>",
"Volker Mische <volker.mische@gmail.com>",
"Richard Littauer <richard.littauer@gmail.com>",
"Thomas Eizinger <thomas@eizinger.io>",
"Ryan Bell <ryan@piing.net>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>",
"Andrew Nesbitt <andrewnez@gmail.com>",
"Elven <mon.samuel@qq.com>",
"Didrik Nordström <didrik.nordstrom@gmail.com>",
"Tiago Alves <alvesjtiago@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Giovanni T. Parra <fiatjaf@gmail.com>",
"Ryan Bell <ryan@piing.net>",
"Thomas Eizinger <thomas@eizinger.io>",
"Didrik Nordström <didrik@betamos.se>",
"Francis Gulotta <wizard@roborooter.com>",
"Henrique Dias <hacdias@gmail.com>",
"Irakli Gozalishvili <rfobic@gmail.com>",
"Florian-Merle <florian.david.merle@gmail.com>",
"Joel Gustafson <joelg@mit.edu>",
"Julien Bouquillon <contact@revolunet.com>",
"Kevin Kwok <antimatter15@gmail.com>",
"Felipe Martins <felipebrasil93@gmail.com>",
"Nuno Nogueira <nunofmn@gmail.com>",
"Fei Liu <liu.feiwood@gmail.com>",
"RasmusErik Voel Jensen <github@solsort.com>",
"Dmitriy Ryajov <dryajov@gmail.com>",
"Soeren <nikorpoulsen@gmail.com>",
"Sönke Hahn <soenkehahn@gmail.com>"
"Sönke Hahn <soenkehahn@gmail.com>",
"Tiago Alves <alvesjtiago@gmail.com>",
"Diogo Silva <fsdiogo@gmail.com>",
"Yusef Napora <yusef@napora.org>",
"Zane Starr <zcstarr@gmail.com>",
"Daijiro Wachi <daijiro.wachi@gmail.com>",
"Chris Bratlien <chrisbratlien@gmail.com>",
"ebinks <elizabethjbinks@gmail.com>",
"Bernd Strehl <bernd.strehl@gmail.com>",
"isan_rivkin <isanrivkin@gmail.com>",
"Henrique Dias <hacdias@gmail.com>",
"robertkiel <robert.kiel@validitylabs.org>",
"Irakli Gozalishvili <rfobic@gmail.com>"
]
}

View File

@@ -73,7 +73,21 @@ class Dialer {
if (!dialTarget.addrs.length) {
throw errCode(new Error('The dial request has no addresses'), codes.ERR_NO_VALID_ADDRESSES)
}
const pendingDial = this._pendingDials.get(dialTarget.id) || this._createPendingDial(dialTarget, options)
// Used for subsequent dials pending
let subsequentDialAborted = false
const onAbort = () => {
subsequentDialAborted = true
pendingDial.controller.abort()
}
let pendingDial = this._pendingDials.get(dialTarget.id)
if (!pendingDial) {
pendingDial = this._createPendingDial(dialTarget, options)
} else {
// track subsequent dial abort
options.signal && options.signal.addEventListener('abort', onAbort)
}
try {
const connection = await pendingDial.promise
@@ -81,12 +95,16 @@ class Dialer {
return connection
} catch (err) {
// Error is a timeout
if (pendingDial.controller.signal.aborted) {
if (pendingDial.controller.signal.aborted && !subsequentDialAborted) {
err.code = codes.ERR_TIMEOUT
// Error is a subsequent dial abort
} else if (subsequentDialAborted) {
err.code = codes.ERR_SUBSEQUENT_DIAL_ABORT
}
log.error(err)
throw err
} finally {
options.signal && options.signal.removeEventListener('abort', onAbort)
pendingDial.destroy()
}
}

View File

@@ -24,6 +24,7 @@ exports.codes = {
ERR_INVALID_PEER: 'ERR_INVALID_PEER',
ERR_MUXER_UNAVAILABLE: 'ERR_MUXER_UNAVAILABLE',
ERR_TIMEOUT: 'ERR_TIMEOUT',
ERR_SUBSEQUENT_DIAL_ABORT: 'ERR_SUBSEQUENT_DIAL_ABORT',
ERR_TRANSPORT_UNAVAILABLE: 'ERR_TRANSPORT_UNAVAILABLE',
ERR_TRANSPORT_DIAL_FAILED: 'ERR_TRANSPORT_DIAL_FAILED',
ERR_UNSUPPORTED_PROTOCOL: 'ERR_UNSUPPORTED_PROTOCOL',

View File

@@ -1,6 +1,8 @@
'use strict'
const libp2pVersion = require('../../package.json').version
module.exports.PROTOCOL_VERSION = 'ipfs/0.1.0'
module.exports.AGENT_VERSION = 'js-libp2p/0.1.0'
module.exports.AGENT_VERSION = `js-libp2p/${libp2pVersion}`
module.exports.MULTICODEC_IDENTIFY = '/ipfs/id/1.0.0'
module.exports.MULTICODEC_IDENTIFY_PUSH = '/ipfs/id/push/1.0.0'

View File

@@ -175,6 +175,7 @@ class IdentifyService {
// Update peers data in PeerStore
this.peerStore.addressBook.set(id, listenAddrs.map((addr) => multiaddr(addr)))
this.peerStore.protoBook.set(id, protocols)
this.peerStore.metadataBook.set(id, 'AgentVersion', Buffer.from(message.agentVersion))
// TODO: Track our observed address so that we can score it
log('received observed address of %s', observedAddr)

View File

@@ -82,7 +82,7 @@ class Libp2p extends EventEmitter {
}
// Create keychain
if (this._options.keychain && this._options.keychain.pass && this._options.keychain.datastore) {
if (this._options.keychain && this._options.keychain.datastore) {
log('creating keychain')
const keychainOpts = Keychain.generateOptions()

View File

@@ -110,7 +110,7 @@ class Keychain {
this.opts = mergeOptions(defaultOptions, options)
// Enforce NIST SP 800-132
if (!this.opts.passPhrase || this.opts.passPhrase.length < 20) {
if (this.opts.passPhrase && this.opts.passPhrase.length < 20) {
throw new Error('passPhrase must be least 20 characters')
}
if (this.opts.dek.keyLength < NIST.minKeyLength) {
@@ -123,13 +123,13 @@ class Keychain {
throw new Error(`dek.iterationCount must be least ${NIST.minIterationCount}`)
}
// Create the derived encrypting key
const dek = crypto.pbkdf2(
const dek = this.opts.passPhrase ? crypto.pbkdf2(
this.opts.passPhrase,
this.opts.dek.salt,
this.opts.dek.iterationCount,
this.opts.dek.keyLength,
this.opts.dek.hash)
this.opts.dek.hash) : ''
Object.defineProperty(this, '_', { value: () => dek })
}

View File

@@ -15,6 +15,7 @@ const { NOISE: Crypto } = require('libp2p-noise')
const multiaddr = require('multiaddr')
const PeerId = require('peer-id')
const AggregateError = require('aggregate-error')
const AbortController = require('abort-controller')
const { AbortError } = require('libp2p-interfaces/src/transport/errors')
const { codes: ErrorCodes } = require('../../src/errors')
@@ -35,8 +36,10 @@ const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
describe('Dialing (direct, WebSockets)', () => {
let localTM
let peerStore
let peerId
before(() => {
before(async () => {
[peerId] = await createPeerId()
peerStore = new PeerStore()
localTM = new TransportManager({
libp2p: {},
@@ -181,6 +184,47 @@ describe('Dialing (direct, WebSockets)', () => {
.and.to.have.property('code', ErrorCodes.ERR_TIMEOUT)
})
it('should abort subsequent dials', async () => {
const dialer = new Dialer({
transportManager: localTM,
timeout: 1000,
peerStore: {
addressBook: {
add: () => { },
getMultiaddrsForPeer: () => [remoteAddr]
}
}
})
const controller = new AbortController()
const deferredDial = pDefer()
const deferredAbort = pDefer()
sinon.stub(localTM, 'dial').callsFake(async (_, options) => {
deferredDial.resolve()
expect(options.signal).to.exist()
expect(options.signal.aborted).to.equal(false)
await deferredAbort.promise
expect(options.signal.aborted).to.equal(true)
throw new AbortError()
})
const dialPromise1 = dialer.connectToPeer(peerId)
const dialPromise2 = dialer.connectToPeer(peerId, { signal: controller.signal })
await deferredDial.promise
controller.abort()
deferredAbort.resolve()
await expect(dialPromise1)
.to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.ERR_SUBSEQUENT_DIAL_ABORT)
await expect(dialPromise2)
.to.eventually.be.rejected()
.and.to.have.property('code', ErrorCodes.ERR_SUBSEQUENT_DIAL_ABORT)
})
it('should dial to the max concurrency', async () => {
const dialer = new Dialer({
transportManager: localTM,

View File

@@ -19,6 +19,7 @@ const { IdentifyService, multicodecs } = require('../../src/identify')
const Peers = require('../fixtures/peers')
const Libp2p = require('../../src')
const baseOptions = require('../utils/base-options.browser')
const pkg = require('../../package.json')
const { MULTIADDRS_WEBSOCKETS } = require('../fixtures/browser')
const remoteAddr = MULTIADDRS_WEBSOCKETS[0]
@@ -53,6 +54,9 @@ describe('Identify', () => {
},
protoBook: {
set: () => { }
},
metadataBook: {
set: () => { }
}
},
multiaddrs: []
@@ -77,6 +81,7 @@ describe('Identify', () => {
sinon.spy(localIdentify.peerStore.addressBook, 'set')
sinon.spy(localIdentify.peerStore.protoBook, 'set')
sinon.spy(localIdentify.peerStore.metadataBook, 'set')
// Run identify
await Promise.all([
@@ -90,6 +95,12 @@ describe('Identify', () => {
expect(localIdentify.peerStore.addressBook.set.callCount).to.equal(1)
expect(localIdentify.peerStore.protoBook.set.callCount).to.equal(1)
const metadataArgs = localIdentify.peerStore.metadataBook.set.firstCall.args
expect(metadataArgs[0].id.bytes).to.equal(remotePeer.bytes)
expect(metadataArgs[1]).to.equal('AgentVersion')
expect(metadataArgs[2].toString()).to.equal(`js-libp2p/${pkg.version}`)
// Validate the remote peer gets updated in the peer store
const call = localIdentify.peerStore.addressBook.set.firstCall
expect(call.args[0].id.bytes).to.equal(remotePeer.bytes)

View File

@@ -2,10 +2,8 @@
/* eslint-env mocha */
'use strict'
const chai = require('chai')
const { expect } = chai
const { chai, expect } = require('aegir/utils/chai')
const fail = expect.fail
chai.use(require('dirty-chai'))
chai.use(require('chai-string'))
const peerUtils = require('../utils/creators/peer')
@@ -40,8 +38,8 @@ describe('keychain', () => {
emptyKeystore = new Keychain(datastore1, { passPhrase: passPhrase })
})
it('needs a pass phrase to encrypt a key', () => {
expect(() => new Keychain(datastore2)).to.throw()
it('can start without a password', () => {
expect(() => new Keychain(datastore2)).to.not.throw()
})
it('needs a NIST SP 800-132 non-weak pass phrase', () => {
@@ -56,12 +54,48 @@ describe('keychain', () => {
expect(Keychain.options).to.exist()
})
it('needs a supported hashing alorithm', () => {
it('supports supported hashing alorithms', () => {
const ok = new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'sha2-256' } })
expect(ok).to.exist()
})
it('does not support unsupported hashing alorithms', () => {
expect(() => new Keychain(datastore2, { passPhrase: passPhrase, dek: { hash: 'my-hash' } })).to.throw()
})
it('can list keys without a password', async () => {
const keychain = new Keychain(datastore2)
expect(await keychain.listKeys()).to.have.lengthOf(0)
})
it('can find a key without a password', async () => {
const keychain = new Keychain(datastore2)
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
const id = `key-${Math.random()}`
await keychainWithPassword.createKey(id, 'rsa', 2048)
await expect(keychain.findKeyById(id)).to.eventually.be.ok()
})
it('can remove a key without a password', async () => {
const keychainWithoutPassword = new Keychain(datastore2)
const keychainWithPassword = new Keychain(datastore2, { passPhrase: `hello-${Date.now()}-${Date.now()}` })
const name = `key-${Math.random()}`
expect(await keychainWithPassword.createKey(name, 'rsa', 2048)).to.have.property('name', name)
expect(await keychainWithoutPassword.findKeyByName(name)).to.have.property('name', name)
await keychainWithoutPassword.removeKey(name)
await expect(keychainWithoutPassword.findKeyByName(name)).to.be.rejectedWith(/does not exist/)
})
it('requires a key to create a password', async () => {
const keychain = new Keychain(datastore2)
await expect(keychain.createKey('derp')).to.be.rejected()
})
it('can generate options', () => {
const options = Keychain.generateOptions()
options.passPhrase = passPhrase
@@ -475,7 +509,7 @@ describe('libp2p.keychain', () => {
throw new Error('should throw an error using the keychain if no passphrase provided')
})
it('can be used if a passphrase is provided', async () => {
it('can be used when a passphrase is provided', async () => {
const [libp2p] = await peerUtils.createPeer({
started: false,
config: {
@@ -492,6 +526,22 @@ describe('libp2p.keychain', () => {
expect(kInfo).to.exist()
})
it('does not require a keychain passphrase', async () => {
const [libp2p] = await peerUtils.createPeer({
started: false,
config: {
keychain: {
datastore: new MemoryDatastore()
}
}
})
await libp2p.loadKeychain()
const kInfo = await libp2p.keychain.createKey('keyName', 'ed25519')
expect(kInfo).to.exist()
})
it('can reload keys', async () => {
const datastore = new MemoryDatastore()
const [libp2p] = await peerUtils.createPeer({