Skip to content
This repository has been archived by the owner on Feb 24, 2021. It is now read-only.

Commit

Permalink
test: add verify inbound and outbound secio
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobheun committed Nov 21, 2019
1 parent 840c217 commit 95c9991
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"it-buffer": "^0.1.1",
"it-length-prefixed": "^3.0.0",
"it-pair": "^1.0.0",
"it-pb-rpc": "jacobheun/it-pb-rpc#master",
"it-pb-rpc": "^0.1.4",
"it-pipe": "^1.1.0",
"libp2p-crypto": "~0.17.1",
"libp2p-interfaces": "~0.1.3",
Expand Down
3 changes: 1 addition & 2 deletions src/handshake/crypto.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
'use strict'

const protons = require('protons')
const PeerId = require('peer-id')
const crypto = require('libp2p-crypto')
const debug = require('debug')
const log = debug('libp2p:secio')
log.error = debug('libp2p:secio:error')

const pbm = protons(require('./secio.proto'))
const pbm = require('./secio.proto')

const support = require('../support')

Expand Down
6 changes: 4 additions & 2 deletions src/handshake/secio.proto.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict'

module.exports = `message Propose {
const protons = require('protons')

module.exports = protons(`message Propose {
optional bytes rand = 1;
optional bytes pubkey = 2;
optional string exchanges = 3;
Expand All @@ -11,4 +13,4 @@ module.exports = `message Propose {
message Exchange {
optional bytes epubkey = 1;
optional bytes signature = 2;
}`
}`)
23 changes: 23 additions & 0 deletions test/fixtures/peer.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

196 changes: 196 additions & 0 deletions test/secio.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/* 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 { Exchange, Propose } = require('../src/handshake/secio.proto')

/**
* TODO:
* Both sides send their proposals,
* then, their exchanges
* then, encrypted nonces for verification
* 1. Proposal A
* 2. Proposal B
* 3. Exchange B
* 4. Exchange A
* 5. Encrypted Nonce A
* 6. Encrypted Nonce B
*
B->F Data 375
F->B Data 375
F->B Data 330
B->F Data 330
F->B Data 52
B->F Data 52
*/

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()
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
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()
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
const { value: ourNonce } = await unbox([ourNonceRaw]).next()

// Verify our nonce is correct
expect(ourNonce.slice()).to.eql(state.proposal.out.rand)
})()
])
})
})

0 comments on commit 95c9991

Please sign in to comment.