js-libp2p-secio/test/secio.spec.js
Maciej Krüger 8ad4c15562 refactor: use async await (#108)
* 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
2019-11-22 14:18:17 -06:00

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)
})()
])
})
})