mirror of
https://github.com/fluencelabs/js-libp2p-secio
synced 2025-03-15 18:00:52 +00:00
* chore: upgrade deps * feat: first iteration of the idea * feat: iterate a layer deeper * feat: rewrite handshake * feat: rewrite propose * feat: rewrite finish * feat: rewrite exchange * feat: rewrite low-level stuff * feat: work on rewriting tests * refactor: browser tests * refactor: .aegir.js * feat: refactor benchmarks * fix: try to make it work * fix: lint * refactor: move tests * refactor: switch deps * refactor: entry file * refactor: a bit more * fix: tests * feat: inital iterables refactor * refactor: streaming * refactor: cleanup * fix: turn bufferlist into buffer * fix: use errors from interfaces * refactor: etm * fix: typo * fix: .read error * fix: satisfy output expectations * fix: it works - WARNING: using varint instead of fixed lp, tests lie * fix: use errors * refactor: benchmarks * fix: add suggestions from review Co-Authored-By: Jacob Heun <jacobheun@gmail.com> * fix: upgrade deps and use correct lp-encoder * refactor: apply changes from review * refactor: apply changes from review * refactor: apply changes from review * chore: remove old tests test: add support tests back * test: fix async benchmarks * chore: clean up deps * fix: use fixed encoding/decoding everywhere fix: exchange final nonce handshake over encryption * test: add verify inbound and outbound secio * test: verify nonces are boxed * chore: add node 12 to ci
181 lines
7.3 KiB
JavaScript
181 lines
7.3 KiB
JavaScript
/* eslint-env mocha */
|
|
'use strict'
|
|
|
|
const chai = require('chai')
|
|
const dirtyChai = require('dirty-chai')
|
|
const expect = chai.expect
|
|
chai.use(dirtyChai)
|
|
|
|
const duplexPair = require('it-pair/duplex')
|
|
const Handshake = require('it-pb-rpc')
|
|
const Secio = require('../src')
|
|
const { createPeerIdsFromFixtures } = require('./fixtures/peer')
|
|
const {
|
|
createExchange,
|
|
createProposal,
|
|
generateKeys,
|
|
identify,
|
|
selectProtocols,
|
|
verify
|
|
} = require('../src/handshake/crypto')
|
|
const { createBoxStream, createUnboxStream } = require('../src/etm')
|
|
const State = require('../src/state')
|
|
const { Propose } = require('../src/handshake/secio.proto')
|
|
|
|
describe('secio', () => {
|
|
let remotePeer
|
|
let localPeer
|
|
|
|
before(async () => {
|
|
[remotePeer, localPeer] = await createPeerIdsFromFixtures(2)
|
|
})
|
|
|
|
it('performs a spec compliant inbound exchange', async () => {
|
|
const [inboundConnection, outboundConnection] = duplexPair()
|
|
await Promise.all([
|
|
Secio.secureInbound(remotePeer, inboundConnection, null),
|
|
(async () => {
|
|
const wrap = Handshake(outboundConnection)
|
|
const state = new State(localPeer, remotePeer)
|
|
|
|
// Create our proposal
|
|
const proposal = createProposal(state)
|
|
|
|
// Send our proposal
|
|
const proposalLength = Buffer.allocUnsafe(4)
|
|
proposalLength.writeInt32BE(proposal.length, 0)
|
|
wrap.write(Buffer.concat([proposalLength, proposal]))
|
|
|
|
// Read their proposal
|
|
let theirProposalRaw = (await wrap.read()).slice()
|
|
let dataLength = theirProposalRaw.readInt32BE(0)
|
|
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
|
|
const theirProposal = Propose.decode(theirProposalRaw)
|
|
expect(theirProposal.rand).to.have.length(16)
|
|
expect(theirProposal.pubkey).to.eql(remotePeer.pubKey.bytes)
|
|
expect(theirProposal.exchanges).to.equal('P-256,P-384,P-521')
|
|
expect(theirProposal.ciphers).to.equal('AES-256,AES-128')
|
|
expect(theirProposal.hashes).to.equal('SHA256,SHA512')
|
|
|
|
// Select protocols
|
|
identify(state, theirProposalRaw)
|
|
await selectProtocols(state)
|
|
expect(state.protocols.local).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
|
|
expect(state.protocols.remote).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
|
|
|
|
// Create our exchange
|
|
const exchange = await createExchange(state)
|
|
|
|
// Send our exchange
|
|
const exchangeLength = Buffer.allocUnsafe(4)
|
|
exchangeLength.writeInt32BE(exchange.length, 0)
|
|
wrap.write(Buffer.concat([exchangeLength, exchange]))
|
|
|
|
// Read their exchange
|
|
let theirExchangeRaw = (await wrap.read()).slice()
|
|
dataLength = theirExchangeRaw.readInt32BE(0)
|
|
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
|
|
await verify(state, theirExchangeRaw)
|
|
|
|
// Generate the crypto keys
|
|
await generateKeys(state)
|
|
|
|
// Create the crypto stream
|
|
const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
|
|
const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
|
|
|
|
// Send back their nonce over the crypto stream
|
|
const { value: nonce } = await box([state.proposal.in.rand]).next()
|
|
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
|
|
const nonceLength = Buffer.allocUnsafe(4)
|
|
nonceLength.writeInt32BE(nonce.length, 0)
|
|
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))
|
|
|
|
// Read our nonce from the crypto stream
|
|
let ourNonceRaw = (await wrap.read())
|
|
dataLength = ourNonceRaw.readInt32BE(0)
|
|
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
|
|
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
|
|
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
|
|
|
|
// Verify our nonce is correct
|
|
expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
|
|
})()
|
|
])
|
|
})
|
|
|
|
it('performs a spec compliant outbound exchange', async () => {
|
|
const [inboundConnection, outboundConnection] = duplexPair()
|
|
await Promise.all([
|
|
Secio.secureOutbound(localPeer, outboundConnection, remotePeer),
|
|
(async () => {
|
|
const wrap = Handshake(inboundConnection)
|
|
const state = new State(remotePeer, localPeer)
|
|
|
|
// Create our proposal
|
|
const proposal = createProposal(state)
|
|
|
|
// Send our proposal
|
|
const proposalLength = Buffer.allocUnsafe(4)
|
|
proposalLength.writeInt32BE(proposal.length, 0)
|
|
wrap.write(Buffer.concat([proposalLength, proposal]))
|
|
|
|
// Read their proposal
|
|
let theirProposalRaw = (await wrap.read()).slice()
|
|
let dataLength = theirProposalRaw.readInt32BE(0)
|
|
theirProposalRaw = theirProposalRaw.slice(4, dataLength + 4)
|
|
const theirProposal = Propose.decode(theirProposalRaw)
|
|
expect(theirProposal.rand).to.have.length(16)
|
|
expect(theirProposal.pubkey).to.eql(localPeer.pubKey.bytes)
|
|
expect(theirProposal.exchanges).to.equal('P-256,P-384,P-521')
|
|
expect(theirProposal.ciphers).to.equal('AES-256,AES-128')
|
|
expect(theirProposal.hashes).to.equal('SHA256,SHA512')
|
|
|
|
// Select protocols
|
|
identify(state, theirProposalRaw)
|
|
await selectProtocols(state)
|
|
expect(state.protocols.local).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
|
|
expect(state.protocols.remote).to.include({ curveT: 'P-256', cipherT: 'AES-256', hashT: 'SHA256' })
|
|
|
|
// Create our exchange
|
|
const exchange = await createExchange(state)
|
|
|
|
// Send our exchange
|
|
const exchangeLength = Buffer.allocUnsafe(4)
|
|
exchangeLength.writeInt32BE(exchange.length, 0)
|
|
wrap.write(Buffer.concat([exchangeLength, exchange]))
|
|
|
|
// Read their exchange
|
|
let theirExchangeRaw = (await wrap.read()).slice()
|
|
dataLength = theirExchangeRaw.readInt32BE(0)
|
|
theirExchangeRaw = theirExchangeRaw.slice(4, dataLength + 4)
|
|
await verify(state, theirExchangeRaw)
|
|
|
|
// Generate the crypto keys
|
|
await generateKeys(state)
|
|
|
|
// Create the crypto stream
|
|
const box = createBoxStream(state.protocols.local.cipher, state.protocols.local.mac)
|
|
const unbox = createUnboxStream(state.protocols.remote.cipher, state.protocols.remote.mac)
|
|
|
|
// Send back their nonce over the crypto stream
|
|
const { value: nonce } = await box([state.proposal.in.rand]).next()
|
|
expect(nonce.slice()).to.not.eql(state.proposal.in.rand) // The nonce should be encrypted
|
|
const nonceLength = Buffer.allocUnsafe(4)
|
|
nonceLength.writeInt32BE(nonce.length, 0)
|
|
wrap.write(Buffer.concat([nonceLength, nonce.slice()]))
|
|
|
|
// Read our nonce from the crypto stream
|
|
let ourNonceRaw = (await wrap.read())
|
|
dataLength = ourNonceRaw.readInt32BE(0)
|
|
ourNonceRaw = ourNonceRaw.shallowSlice(4, dataLength + 4) // Unbox expects a BufferList, so shallow slice here
|
|
expect(ourNonceRaw.slice()).to.not.eql(state.proposal.out.rand) // The nonce should be encrypted
|
|
const { value: ourNonce } = await unbox([ourNonceRaw]).next()
|
|
|
|
// Verify our nonce is correct
|
|
expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
|
|
})()
|
|
])
|
|
})
|
|
})
|