mirror of
https://github.com/fluencelabs/js-libp2p-interfaces
synced 2025-07-07 12:11:37 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
b960f29757 | |||
5206862504 | |||
749a8d035d | |||
763187beb1 | |||
44e08c9007 | |||
d2fe2d1b36 | |||
bcb52ae709 | |||
c52c3dc8a3 | |||
92f13f8106 | |||
5a5c44a770 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@
|
||||
|
||||
build
|
||||
dist
|
||||
docs
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -1,3 +1,33 @@
|
||||
<a name="0.1.3"></a>
|
||||
## [0.1.3](https://github.com/libp2p/js-interfaces/compare/v0.1.2...v0.1.3) (2019-10-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* localAddr should be optional ([#6](https://github.com/libp2p/js-interfaces/issues/6)) ([749a8d0](https://github.com/libp2p/js-interfaces/commit/749a8d0))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.2"></a>
|
||||
## [0.1.2](https://github.com/libp2p/js-interfaces/compare/v0.1.1...v0.1.2) (2019-10-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* crypto errors ([#4](https://github.com/libp2p/js-interfaces/issues/4)) ([d2fe2d1](https://github.com/libp2p/js-interfaces/commit/d2fe2d1))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.1"></a>
|
||||
## [0.1.1](https://github.com/libp2p/js-interfaces/compare/v0.1.0...v0.1.1) (2019-10-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* crypto interface ([#2](https://github.com/libp2p/js-interfaces/issues/2)) ([5a5c44a](https://github.com/libp2p/js-interfaces/commit/5a5c44a))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# 0.1.0 (2019-10-20)
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
- [Connection](./src/connection)
|
||||
- [Content Routing](./src/content-routing)
|
||||
- [Crypto](./src/crypto)
|
||||
- [Peer Discovery](./src/peer-discovery)
|
||||
- [Peer Routing](./src/peer-routing)
|
||||
- [Stream Muxer](./src/stream-muxer)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "libp2p-interfaces",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.3",
|
||||
"description": "Interfaces for JS Libp2p",
|
||||
"main": "src/index.js",
|
||||
"files": [
|
||||
@ -51,6 +51,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"aegir": "^20.4.1",
|
||||
"it-handshake": "^1.0.0",
|
||||
"it-pair": "^1.0.0",
|
||||
"it-pipe": "^1.0.1",
|
||||
"peer-info": "^0.17.0"
|
||||
|
@ -109,7 +109,7 @@ const conn = new Connection({
|
||||
|
||||
Creates a new Connection instance.
|
||||
|
||||
`localAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote.
|
||||
`localAddr` is the optional [multiaddr](https://github.com/multiformats/multiaddr) address used by the local peer to reach the remote.
|
||||
`remoteAddr` is the [multiaddr](https://github.com/multiformats/multiaddr) address used to communicate with the remote peer.
|
||||
`localPeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the local peer.
|
||||
`remotePeer` is the [PeerId](https://github.com/libp2p/js-peer-id) of the remote peer.
|
||||
|
@ -16,7 +16,7 @@ class Connection {
|
||||
/**
|
||||
* Creates an instance of Connection.
|
||||
* @param {object} properties properties of the connection.
|
||||
* @param {multiaddr} properties.localAddr local multiaddr of the connection.
|
||||
* @param {multiaddr} [properties.localAddr] local multiaddr of the connection if known.
|
||||
* @param {multiaddr} properties.remoteAddr remote multiaddr of the connection.
|
||||
* @param {PeerId} properties.localPeer local peer-id.
|
||||
* @param {PeerId} properties.remotePeer remote peer-id.
|
||||
@ -32,7 +32,7 @@ class Connection {
|
||||
* @param {string} [properties.stat.encryption] connection encryption method identifier.
|
||||
*/
|
||||
constructor ({ localAddr, remoteAddr, localPeer, remotePeer, newStream, close, getStreams, stat }) {
|
||||
assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
|
||||
localAddr && assert(multiaddr.isMultiaddr(localAddr), 'localAddr must be an instance of multiaddr')
|
||||
assert(multiaddr.isMultiaddr(remoteAddr), 'remoteAddr must be an instance of multiaddr')
|
||||
assert(PeerId.isPeerId(localPeer), 'localPeer must be an instance of peer-id')
|
||||
assert(PeerId.isPeerId(remotePeer), 'remotePeer must be an instance of peer-id')
|
||||
|
98
src/crypto/README.md
Normal file
98
src/crypto/README.md
Normal file
@ -0,0 +1,98 @@
|
||||
interface-crypto
|
||||
==================
|
||||
|
||||
> A test suite you can use to implement a libp2p crypto module. A libp2p crypto module is used to ensure all exchanged data between two peers is encrypted.
|
||||
|
||||
**Modules that implement the interface**
|
||||
|
||||
- [js-libp2p-secio](https://github.com/libp2p/js-libp2p-secio)
|
||||
|
||||
## Table of Contents
|
||||
- [interface-crypto](#interface-crypto)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Using the Test Suite](#using-the-test-suite)
|
||||
- [API](#api)
|
||||
- [Secure Inbound](#secure-inbound)
|
||||
- [Secure Outbound](#secure-outbound)
|
||||
- [Crypto Errors](#crypto-errors)
|
||||
- [Error Types](#error-types)
|
||||
|
||||
## Using the Test Suite
|
||||
|
||||
You can also check out the [internal test suite](../../test/crypto/compliance.spec.js) to see the setup in action.
|
||||
|
||||
```js
|
||||
const tests = require('libp2p-interfaces/src/crypto/tests')
|
||||
const yourCrypto = require('./your-crypto')
|
||||
|
||||
tests({
|
||||
setup () {
|
||||
// Set up your crypto if needed, then return it
|
||||
return yourCrypto
|
||||
},
|
||||
teardown () {
|
||||
// Clean up your crypto if needed
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
- `Crypto`
|
||||
- `protocol<string>`: The protocol id of the crypto module.
|
||||
- `secureInbound<function(PeerId, duplex)>`: Secures inbound connections.
|
||||
- `secureOutbound<function(PeerId, duplex, PeerId)>`: Secures outbound connections.
|
||||
|
||||
### Secure Inbound
|
||||
|
||||
- `const { conn, remotePeer } = await crypto.secureInbound(localPeer, duplex, [remotePeer])`
|
||||
|
||||
Secures an inbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer.
|
||||
|
||||
**Parameters**
|
||||
- `localPeer` is the [PeerId][peer-id] of the receiving peer.
|
||||
- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encryption.
|
||||
- `remotePeer` is the optional [PeerId][peer-id] of the initiating peer, if known. This may only exist during transport upgrades.
|
||||
|
||||
**Return Value**
|
||||
- `<object>`
|
||||
- `conn<duplex>`: An encrypted [streaming iterable duplex][iterable-duplex].
|
||||
- `remotePeer<PeerId>`: The [PeerId][peer-id] of the remote peer.
|
||||
|
||||
### Secure Outbound
|
||||
|
||||
- `const { conn, remotePeer } = await crypto.secureOutbound(localPeer, duplex, remotePeer)`
|
||||
|
||||
Secures an outbound [streaming iterable duplex][iterable-duplex] connection. It returns an encrypted [streaming iterable duplex][iterable-duplex], as well as the [PeerId][peer-id] of the remote peer.
|
||||
|
||||
**Parameters**
|
||||
- `localPeer` is the [PeerId][peer-id] of the receiving peer.
|
||||
- `duplex` is the [streaming iterable duplex][iterable-duplex] that will be encrypted.
|
||||
- `remotePeer` is the [PeerId][peer-id] of the remote peer. If provided, implementations **should** use this to validate the integrity of the remote peer.
|
||||
|
||||
**Return Value**
|
||||
- `<object>`
|
||||
- `conn<duplex>`: An encrypted [streaming iterable duplex][iterable-duplex].
|
||||
- `remotePeer<PeerId>`: The [PeerId][peer-id] of the remote peer. This **should** match the `remotePeer` parameter, and implementations should enforce this.
|
||||
|
||||
[peer-id]: https://github.com/libp2p/js-peer-id
|
||||
[iterable-duplex]: https://gist.github.com/alanshaw/591dc7dd54e4f99338a347ef568d6ee9#duplex-it
|
||||
|
||||
## Crypto Errors
|
||||
|
||||
Common crypto errors come with the interface, and can be imported directly. All Errors take an optional message.
|
||||
|
||||
```js
|
||||
const {
|
||||
InvalidCryptoExchangeError,
|
||||
UnexpectedPeerError
|
||||
} = require('libp2p-interfaces/src/crypto/errors')
|
||||
|
||||
const error = new UnexpectedPeerError('a custom error message')
|
||||
console.log(error.code === UnexpectedPeerError.code) // true
|
||||
```
|
||||
|
||||
### Error Types
|
||||
|
||||
- `InvalidCryptoExchangeError` - Should be thrown when a peer provides data that is insufficient to finish the crypto exchange.
|
||||
- `UnexpectedPeerError` - Should be thrown when the expected peer id does not match the peer id determined via the crypto exchange.
|
28
src/crypto/errors.js
Normal file
28
src/crypto/errors.js
Normal file
@ -0,0 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
class UnexpectedPeerError extends Error {
|
||||
constructor (message = 'Unexpected Peer') {
|
||||
super(message)
|
||||
this.code = UnexpectedPeerError.code
|
||||
}
|
||||
|
||||
static get code () {
|
||||
return 'ERR_UNEXPECTED_PEER'
|
||||
}
|
||||
}
|
||||
|
||||
class InvalidCryptoExchangeError extends Error {
|
||||
constructor (message = 'Invalid crypto exchange') {
|
||||
super(message)
|
||||
this.code = InvalidCryptoExchangeError.code
|
||||
}
|
||||
|
||||
static get code () {
|
||||
return 'ERR_INVALID_CRYPTO_EXCHANGE'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
UnexpectedPeerError,
|
||||
InvalidCryptoExchangeError
|
||||
}
|
102
src/crypto/tests/index.js
Normal file
102
src/crypto/tests/index.js
Normal file
@ -0,0 +1,102 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const duplexPair = require('it-pair/duplex')
|
||||
const pipe = require('it-pipe')
|
||||
const peers = require('../../utils/peers')
|
||||
const { UnexpectedPeerError } = require('../errors')
|
||||
const PeerId = require('peer-id')
|
||||
const { collect } = require('streaming-iterables')
|
||||
const chai = require('chai')
|
||||
const expect = chai.expect
|
||||
chai.use(require('dirty-chai'))
|
||||
|
||||
module.exports = (common) => {
|
||||
describe('interface-crypto', () => {
|
||||
let crypto
|
||||
let localPeer
|
||||
let remotePeer
|
||||
let mitmPeer
|
||||
|
||||
before(async () => {
|
||||
[
|
||||
crypto,
|
||||
localPeer,
|
||||
remotePeer,
|
||||
mitmPeer
|
||||
] = await Promise.all([
|
||||
common.setup(),
|
||||
PeerId.createFromJSON(peers[0]),
|
||||
PeerId.createFromJSON(peers[1]),
|
||||
PeerId.createFromJSON(peers[2])
|
||||
])
|
||||
})
|
||||
|
||||
after(() => common.teardown && common.teardown())
|
||||
|
||||
it('has a protocol string', () => {
|
||||
expect(crypto.protocol).to.exist()
|
||||
expect(crypto.protocol).to.be.a('string')
|
||||
})
|
||||
|
||||
it('it wraps the provided duplex connection', async () => {
|
||||
const [localConn, remoteConn] = duplexPair()
|
||||
|
||||
const [
|
||||
inboundResult,
|
||||
outboundResult
|
||||
] = await Promise.all([
|
||||
crypto.secureInbound(remotePeer, localConn),
|
||||
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
|
||||
])
|
||||
|
||||
// Echo server
|
||||
pipe(inboundResult.conn, inboundResult.conn)
|
||||
|
||||
// Send some data and collect the result
|
||||
const input = Buffer.from('data to encrypt')
|
||||
const result = await pipe(
|
||||
[input],
|
||||
outboundResult.conn,
|
||||
// Convert BufferList to Buffer via slice
|
||||
(source) => (async function * toBuffer () {
|
||||
for await (const chunk of source) {
|
||||
yield chunk.slice()
|
||||
}
|
||||
})(),
|
||||
collect
|
||||
)
|
||||
|
||||
expect(result).to.eql([input])
|
||||
})
|
||||
|
||||
it('should return the remote peer id', async () => {
|
||||
const [localConn, remoteConn] = duplexPair()
|
||||
|
||||
const [
|
||||
inboundResult,
|
||||
outboundResult
|
||||
] = await Promise.all([
|
||||
crypto.secureInbound(remotePeer, localConn),
|
||||
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
|
||||
])
|
||||
|
||||
// Inbound should return the initiator (local) peer
|
||||
expect(inboundResult.remotePeer.id).to.eql(localPeer.id)
|
||||
// Outbound should return the receiver (remote) peer
|
||||
expect(outboundResult.remotePeer.id).to.eql(remotePeer.id)
|
||||
})
|
||||
|
||||
it('inbound connections should verify peer integrity if known', async () => {
|
||||
const [localConn, remoteConn] = duplexPair()
|
||||
|
||||
await Promise.all([
|
||||
crypto.secureInbound(remotePeer, localConn, mitmPeer),
|
||||
crypto.secureOutbound(localPeer, remoteConn, remotePeer)
|
||||
]).then(expect.fail, (err) => {
|
||||
expect(err).to.exist()
|
||||
expect(err).to.have.property('code', UnexpectedPeerError.code)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
|
||||
const tests = require('../../src/connection/tests')
|
||||
const { Connection } = require('../../src/connection')
|
||||
const peers = require('../utils/peers')
|
||||
const peers = require('../../src/utils/peers')
|
||||
const PeerId = require('peer-id')
|
||||
const multiaddr = require('multiaddr')
|
||||
const pair = require('it-pair')
|
||||
|
13
test/crypto/compliance.spec.js
Normal file
13
test/crypto/compliance.spec.js
Normal file
@ -0,0 +1,13 @@
|
||||
/* eslint-env mocha */
|
||||
'use strict'
|
||||
|
||||
const tests = require('../../src/crypto/tests')
|
||||
const mockCrypto = require('./mock-crypto')
|
||||
|
||||
describe('compliance tests', () => {
|
||||
tests({
|
||||
setup () {
|
||||
return mockCrypto
|
||||
}
|
||||
})
|
||||
})
|
75
test/crypto/mock-crypto.js
Normal file
75
test/crypto/mock-crypto.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict'
|
||||
|
||||
const PeerId = require('peer-id')
|
||||
const handshake = require('it-handshake')
|
||||
const duplexPair = require('it-pair/duplex')
|
||||
const pipe = require('it-pipe')
|
||||
const { UnexpectedPeerError } = require('../../src/crypto/errors')
|
||||
|
||||
// A basic transform that does nothing to the data
|
||||
const transform = () => {
|
||||
return (source) => (async function * () {
|
||||
for await (const chunk of source) {
|
||||
yield chunk
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
protocol: 'insecure',
|
||||
secureInbound: async (localPeer, duplex, expectedPeer) => {
|
||||
// 1. Perform a basic handshake.
|
||||
const shake = handshake(duplex)
|
||||
shake.write(localPeer.id)
|
||||
const remoteId = await shake.read()
|
||||
const remotePeer = new PeerId(remoteId.slice())
|
||||
shake.rest()
|
||||
|
||||
if (expectedPeer && expectedPeer.id !== remotePeer.id) {
|
||||
throw new UnexpectedPeerError()
|
||||
}
|
||||
|
||||
// 2. Create your encryption box/unbox wrapper
|
||||
const wrapper = duplexPair()
|
||||
const encrypt = transform() // Use transform iterables to modify data
|
||||
const decrypt = transform()
|
||||
|
||||
pipe(
|
||||
wrapper[0], // We write to wrapper
|
||||
encrypt, // The data is encrypted
|
||||
shake.stream, // It goes to the remote peer
|
||||
decrypt, // Decrypt the incoming data
|
||||
wrapper[0] // Pipe to the wrapper
|
||||
)
|
||||
|
||||
return {
|
||||
conn: wrapper[1],
|
||||
remotePeer
|
||||
}
|
||||
},
|
||||
secureOutbound: async (localPeer, duplex, remotePeer) => {
|
||||
// 1. Perform a basic handshake.
|
||||
const shake = handshake(duplex)
|
||||
shake.write(localPeer.id)
|
||||
const remoteId = await shake.read()
|
||||
shake.rest()
|
||||
|
||||
// 2. Create your encryption box/unbox wrapper
|
||||
const wrapper = duplexPair()
|
||||
const encrypt = transform()
|
||||
const decrypt = transform()
|
||||
|
||||
pipe(
|
||||
wrapper[0], // We write to wrapper
|
||||
encrypt, // The data is encrypted
|
||||
shake.stream, // It goes to the remote peer
|
||||
decrypt, // Decrypt the incoming data
|
||||
wrapper[0] // Pipe to the wrapper
|
||||
)
|
||||
|
||||
return {
|
||||
conn: wrapper[1],
|
||||
remotePeer: new PeerId(remoteId.slice())
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user