Skip to content
This repository was archived by the owner on Aug 23, 2019. It is now read-only.

Commit ab4075e

Browse files
committed
fix: use xor streaming for encryption
1 parent 91d3c34 commit ab4075e

File tree

8 files changed

+74
-107
lines changed

8 files changed

+74
-107
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
],
2626
"license": "MIT",
2727
"dependencies": {
28+
"debug": "^3.1.0",
2829
"interface-connection": "^0.3.2",
2930
"pull-handshake": "^1.1.4",
30-
"pull-length-prefixed": "^1.3.0",
3131
"pull-stream": "^3.6.7",
32-
"tweetnacl": "^1.0.0"
32+
"xsalsa20": "^1.0.2"
3333
},
3434
"devDependencies": {
3535
"aegir": "^13.1.0",

src/crypto.js

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
'use strict'
22

33
const pull = require('pull-stream')
4-
const lp = require('pull-length-prefixed')
5-
const nacl = require('tweetnacl')
4+
const debug = require('debug')
65
const Errors = require('./errors')
6+
const xsalsa20 = require('xsalsa20')
7+
const KEY_LENGTH = require('./key-generator').KEY_LENGTH
78

8-
const lpOpts = {
9-
fixed: true,
10-
bytes: 4
11-
}
9+
const log = debug('libp2p:pnet')
10+
log.trace = debug('libp2p:pnet:trace')
11+
log.err = debug('libp2p:pnet:err')
1212

1313
/**
1414
* Creates a pull stream to encrypt messages in a private network
@@ -18,14 +18,12 @@ const lpOpts = {
1818
* @returns {PullStream} a through stream
1919
*/
2020
module.exports.createBoxStream = (nonce, psk) => {
21+
const xor = xsalsa20(nonce, psk)
2122
return pull(
2223
ensureBuffer(),
2324
pull.map((chunk) => {
24-
const encryptedChunk = nacl.secretbox(Buffer.from(chunk), nonce, psk)
25-
26-
return Buffer.from(encryptedChunk)
27-
}),
28-
lp.encode(lpOpts)
25+
return xor.update(chunk, chunk)
26+
})
2927
)
3028
}
3129

@@ -37,18 +35,11 @@ module.exports.createBoxStream = (nonce, psk) => {
3735
* @returns {PullStream} a through stream
3836
*/
3937
module.exports.createUnboxStream = (nonce, psk) => {
38+
const xor = xsalsa20(nonce, psk)
4039
return pull(
4140
ensureBuffer(),
42-
lp.decode(lpOpts),
4341
pull.map((chunk) => {
44-
let decryptedChunk = nacl.secretbox.open(Buffer.from(chunk), nonce, psk)
45-
46-
// If it's null, there's most likely a psk mismatch
47-
if (decryptedChunk === null) {
48-
throw new Error(Errors.INVALID_PEER)
49-
}
50-
51-
return Buffer.from(decryptedChunk)
42+
return xor.update(chunk, chunk)
5243
})
5344
)
5445
}
@@ -72,7 +63,7 @@ module.exports.decodeV1PSK = (pskBuffer) => {
7263
const codec = metadata.shift()
7364
const psk = Buffer.from(metadata.shift(), 'hex')
7465

75-
if (psk.byteLength !== nacl.box.sharedKeyLength) {
66+
if (psk.byteLength !== KEY_LENGTH) {
7667
throw new Error(Errors.INVALID_PSK)
7768
}
7869

src/handshake.js

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
'use strict'
22

33
const series = require('async/series')
4-
const lp = require('pull-length-prefixed')
5-
const nacl = require('tweetnacl')
64
const pull = require('pull-stream')
75
const handshake = require('pull-handshake')
6+
const debug = require('debug')
87

98
const crypto = require('./crypto')
109
const Errors = require('./errors')
10+
const NONCE_LENGTH = require('./key-generator').NONCE_LENGTH
11+
12+
const log = debug('libp2p:pnet')
13+
log.err = debug('libp2p:pnet:err')
14+
log.trace = debug('libp2p:pnet:trace')
1115

1216
/**
1317
* Exchanges nonces with a connected peer. The nonce read from the peer
@@ -18,21 +22,13 @@ const Errors = require('./errors')
1822
* @returns {void}
1923
*/
2024
function exchangeNonce (state, callback) {
21-
pull(
22-
// Write the nonce
23-
pull.values([state.local.nonce]),
24-
// Append 4 bytes
25-
lp.encode({fixed: true, bytes: 4}),
26-
// Collect the data and write it to the handshake
27-
pull.collect((err, res) => {
28-
if (err) { return callback(err) }
29-
state.shake.write(res[0])
30-
})
31-
)
25+
// Write the nonce
26+
state.shake.write(state.local.nonce)
3227

3328
// Read the nonce from the handshake
34-
lp.decodeFromReader(state.shake, {fixed: true, bytes: 4}, (err, nonce) => {
29+
state.shake.read(NONCE_LENGTH, (err, nonce) => {
3530
state.remote.nonce = nonce || null
31+
log.trace('nonce exchange complete')
3632
callback(err)
3733
})
3834
}
@@ -46,11 +42,7 @@ function exchangeNonce (state, callback) {
4642
*/
4743
function protectConnection (state, callback) {
4844
const stream = state.shake.rest()
49-
const shake = handshake({ timeout: state.timeout }, (err) => {
50-
if (err) {
51-
callback(err)
52-
}
53-
})
45+
const shake = handshake({ timeout: state.timeout })
5446

5547
// Bridge the handshakes to form the encrypted connection
5648
pull(
@@ -61,23 +53,8 @@ function protectConnection (state, callback) {
6153
stream
6254
)
6355

64-
// Exchange nonces via the handshake
65-
shake.handshake.write(state.local.nonce)
66-
shake.handshake.read(nacl.box.nonceLength, (err, nonceBack) => {
67-
if (err) {
68-
state.secure.resolve({
69-
source: pull.error(err),
70-
sink: () => { }
71-
})
72-
return callback(err)
73-
}
74-
75-
// Resolve the deferred connection with the completed handshake
76-
// This will be set as the inner connection in the final protected
77-
// connection result
78-
state.secure.resolve(shake.handshake.rest())
79-
callback()
80-
})
56+
state.secure.resolve(shake.handshake.rest())
57+
callback()
8158
}
8259

8360
/**
@@ -88,6 +65,8 @@ function protectConnection (state, callback) {
8865
* @returns {PullStream} a handshake stream
8966
*/
9067
module.exports = function handshake (state, callback) {
68+
log.trace('begin handshake')
69+
9170
series([
9271
(cb) => exchangeNonce(state, cb),
9372
(cb) => protectConnection(state, cb)

src/index.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const Errors = require('./errors')
88
const handshake = require('./handshake')
99
const State = require('./state')
1010
const decodeV1PSK = require('./crypto').decodeV1PSK
11+
const debug = require('debug')
12+
const log = debug('libp2p:pnet')
13+
log.err = debug('libp2p:pnet:err')
1114

1215
/**
1316
* Takes a Private Shared Key (psk) and provides a `protect` method
@@ -16,11 +19,12 @@ const decodeV1PSK = require('./crypto').decodeV1PSK
1619
class Protector {
1720
/**
1821
* @param {Buffer} keyBuffer The private shared key buffer
22+
* @param {any} options Options for the protector
1923
* @constructor
2024
*/
21-
constructor (keyBuffer) {
25+
constructor (keyBuffer, options) {
2226
const decodedPSK = decodeV1PSK(keyBuffer)
23-
27+
this.options = options || {}
2428
this.psk = decodedPSK.psk
2529
this.tag = decodedPSK.tag
2630
}
@@ -38,18 +42,22 @@ class Protector {
3842
assert(connection, Errors.NO_HANDSHAKE_CONNECTION)
3943

4044
const protectedConnection = new Connection(undefined, connection)
41-
const state = new State(this.psk, callback)
45+
const state = new State(this.psk, { timeout: this.options.timeout }, callback)
46+
47+
log('protecting the connection')
4248

4349
// Run the connection through an encryption handshake
4450
pull(
4551
connection,
4652
handshake(state, (err) => {
4753
if (err) {
54+
log.err('There was an error attempting to protect the connection', err)
4855
return callback(err)
4956
}
5057

5158
connection.getPeerInfo(() => {
5259
protectedConnection.setInnerConn(new Connection(state.secure, connection))
60+
log('the connection has been successfully wrapped by the protector')
5361
callback()
5462
})
5563
}),

src/key-generator.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
'use strict'
22

33
const crypto = require('crypto')
4-
const nacl = require('tweetnacl')
5-
const KEY_LENGTH = nacl.box.sharedKeyLength
4+
const KEY_LENGTH = 32
65

76
/**
87
* Generates a PSK that can be used in a libp2p-pnet private network
@@ -15,6 +14,8 @@ function generate (writer) {
1514
}
1615

1716
module.exports = generate
17+
module.exports.NONCE_LENGTH = 24
18+
module.exports.KEY_LENGTH = KEY_LENGTH
1819

1920
if (require.main === module) {
2021
generate(process.stdout)

src/state.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,31 @@
22

33
const handshake = require('pull-handshake')
44
const deferred = require('pull-defer')
5-
const nacl = require('tweetnacl')
5+
const crypto = require('crypto')
6+
const NONCE_LENGTH = require('./key-generator').NONCE_LENGTH
67

78
/**
89
* Keeps track of the state of a given connection, such as the local psk
910
* and local and remote nonces for encryption/decryption
1011
*/
1112
class State {
12-
constructor (psk, callback) {
13+
/**
14+
* @param {Buffer} psk The key buffer used for encryption
15+
* @param {any} options Options for the state, including timeout of the handshake
16+
* @param {function(Error)} callback Called on completion of the handshake
17+
* @constructor
18+
*/
19+
constructor (psk, options, callback) {
1320
this.local = {
1421
nonce: Buffer.from(
15-
nacl.randomBytes(nacl.box.nonceLength)
22+
crypto.randomBytes(NONCE_LENGTH)
1623
),
1724
psk: psk
1825
}
26+
options = options || {}
1927

2028
this.remote = { nonce: null }
21-
this.timeout = 60 * 1000
29+
this.timeout = options.timeout || 5e3
2230
this.secure = deferred.duplex()
2331
this.stream = handshake({ timeout: this.timeout }, callback)
2432
this.shake = this.stream.handshake

test/pnet.spec.js

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const pull = require('pull-stream')
1414

1515
const Protector = require('../src')
1616
const Errors = require('../src').errors
17-
const doneAfterN = require('./utils').doneAfterN
1817
const generate = require('../src/key-generator')
1918

2019
const swarmKeyBuffer = Buffer.alloc(95)
@@ -42,20 +41,6 @@ describe('private network', () => {
4241
expect(protector.psk.byteLength).to.equal(32)
4342
})
4443

45-
describe('invalid psks', () => {
46-
it('when the psk file is wrong', () => {
47-
expect(() => {
48-
return new Protector(Buffer.from('not-a-key'))
49-
}).to.throw(Errors.INVALID_PSK)
50-
})
51-
52-
it('when the psk isnt the proper length', () => {
53-
expect(() => {
54-
return new Protector(Buffer.from('/key/swarm/psk/1.0.0/\n/base16/\ndffb7e'))
55-
}).to.throw(Errors.INVALID_PSK)
56-
})
57-
})
58-
5944
it('protects a simple connection', (done) => {
6045
const p = pair()
6146
const protector = new Protector(swarmKeyBuffer)
@@ -86,18 +71,9 @@ describe('private network', () => {
8671
const p = pair()
8772
const protector = new Protector(swarmKeyBuffer)
8873
const protectorB = new Protector(wrongSwarmKeyBuffer)
89-
const multiDone = doneAfterN(2, done)
9074

91-
const aToB = protector.protect(new Connection(p[0]), (err) => {
92-
expect(err).to.exist()
93-
expect(err.message).to.equal(Errors.INVALID_PEER)
94-
multiDone()
95-
})
96-
const bToA = protectorB.protect(new Connection(p[1]), (err) => {
97-
expect(err).to.exist()
98-
expect(err.message).to.equal(Errors.INVALID_PEER)
99-
multiDone()
100-
})
75+
const aToB = protector.protect(new Connection(p[0]), () => { })
76+
const bToA = protectorB.protect(new Connection(p[1]), () => { })
10177

10278
pull(
10379
pull.values([Buffer.from('hello world'), Buffer.from('doo dah')]),
@@ -106,9 +82,24 @@ describe('private network', () => {
10682

10783
pull(
10884
bToA,
109-
pull.collect(() => {
110-
expect.fail()
85+
pull.collect((values) => {
86+
expect(values).to.equal(null)
87+
done()
11188
})
11289
)
11390
})
91+
92+
describe('invalid psks', () => {
93+
it('when the psk file is wrong', () => {
94+
expect(() => {
95+
return new Protector(Buffer.from('not-a-key'))
96+
}).to.throw(Errors.INVALID_PSK)
97+
})
98+
99+
it('when the psk isnt the proper length', () => {
100+
expect(() => {
101+
return new Protector(Buffer.from('/key/swarm/psk/1.0.0/\n/base16/\ndffb7e'))
102+
}).to.throw(Errors.INVALID_PSK)
103+
})
104+
})
114105
})

test/utils/index.js

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)