Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

bittorrent-protocol

Package Overview
Dependencies
Maintainers
8
Versions
100
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bittorrent-protocol - npm Package Compare versions

Comparing version 3.3.2 to 3.4.0

451

index.js

@@ -5,6 +5,10 @@ /*! bittorrent-protocol. MIT License. WebTorrent LLC <https://webtorrent.io/opensource> */

const BitField = require('bitfield').default
const crypto = require('crypto')
const debug = require('debug')('bittorrent-protocol')
const randombytes = require('randombytes')
const sha1 = require('simple-sha1')
const speedometer = require('speedometer')
const stream = require('readable-stream')
const xor = require('buffer-xor')
const RC4 = require('rc4')

@@ -24,2 +28,8 @@ const BITFIELD_GROW = 400000

const DH_PRIME = 'ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a63a36210000000000090563'
const DH_GENERATOR = 2
const VC = Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
const CRYPTO_PROVIDE = Buffer.from([0x00, 0x00, 0x01, 0x02])
const CRYPTO_SELECT = Buffer.from([0x00, 0x00, 0x00, 0x02]) // always try to choose RC4 encryption instead of plaintext
class Request {

@@ -35,3 +45,3 @@ constructor (piece, offset, length, callback) {

class Wire extends stream.Duplex {
constructor () {
constructor (type = null, retries = 0, peEnabled = false) {
super()

@@ -44,3 +54,3 @@

this.peerIdBuffer = null // remote peer id (buffer)
this.type = null // connection type ('webrtc', 'tcpIncoming', 'tcpOutgoing', 'webSeed')
this.type = type // connection type ('webrtc', 'tcpIncoming', 'tcpOutgoing', 'webSeed')

@@ -94,5 +104,35 @@ this.amChoking = true // are we choking the peer?

this._peEnabled = peEnabled
if (peEnabled) {
this._dh = crypto.createDiffieHellman(DH_PRIME, 'hex', DH_GENERATOR) // crypto object used to generate keys/secret
this._myPubKey = this._dh.generateKeys('hex') // my DH public key
} else {
this._myPubKey = null
}
this._peerPubKey = null // peer's DH public key
this._sharedSecret = null // shared DH secret
this._peerCryptoProvide = [] // encryption methods provided by peer; we expect this to always contain 0x02
this._cryptoHandshakeDone = false
this._cryptoSyncPattern = null // the pattern to search for when resynchronizing after receiving pe1/pe2
this._waitMaxBytes = null // the maximum number of bytes resynchronization must occur within
this._encryptionMethod = null // 1 for plaintext, 2 for RC4
this._encryptGenerator = null // RC4 keystream generator for encryption
this._decryptGenerator = null // RC4 keystream generator for decryption
this._setGenerators = false // a flag for whether setEncrypt() has successfully completed
this.once('finish', () => this._onFinish())
this._parseHandshake()
this.on('finish', this._onFinish)
this._debug('type:', this.type)
if (this.type === 'tcpIncoming' && this._peEnabled) {
// If we are not the initiator, we should wait to see if the client begins
// with PE/MSE handshake or the standard bittorrent handshake.
this._determineHandshakeType()
} else if (this.type === 'tcpOutgoing' && this._peEnabled && retries === 0) {
this._parsePe2()
} else {
this._parseHandshake(null)
}
}

@@ -185,2 +225,56 @@

sendPe1 () {
if (this._peEnabled) {
var padALen = Math.floor(Math.random() * 513)
var padA = randombytes(padALen)
this._push(Buffer.concat([Buffer.from(this._myPubKey, 'hex'), padA]))
}
}
sendPe2 () {
var padBLen = Math.floor(Math.random() * 513)
var padB = randombytes(padBLen)
this._push(Buffer.concat([Buffer.from(this._myPubKey, 'hex'), padB]))
}
sendPe3 (infoHash) {
this.setEncrypt(this._sharedSecret, infoHash)
var hash1Buffer = Buffer.from(sha1.sync(Buffer.from(this._utfToHex('req1') + this._sharedSecret, 'hex')), 'hex')
var hash2Buffer = Buffer.from(sha1.sync(Buffer.from(this._utfToHex('req2') + infoHash, 'hex')), 'hex')
var hash3Buffer = Buffer.from(sha1.sync(Buffer.from(this._utfToHex('req3') + this._sharedSecret, 'hex')), 'hex')
var hashesXorBuffer = xor(hash2Buffer, hash3Buffer)
var padCLen = randombytes(2).readUInt16BE(0) % 512
var padCBuffer = randombytes(padCLen)
var vcAndProvideBuffer = Buffer.alloc(8 + 4 + 2 + padCLen + 2)
VC.copy(vcAndProvideBuffer)
CRYPTO_PROVIDE.copy(vcAndProvideBuffer, 8)
vcAndProvideBuffer.writeInt16BE(padCLen, 12) // pad C length
padCBuffer.copy(vcAndProvideBuffer, 14)
vcAndProvideBuffer.writeInt16BE(0, 14 + padCLen) // IA length
vcAndProvideBuffer = this._encryptHandshake(vcAndProvideBuffer)
this._push(Buffer.concat([hash1Buffer, hashesXorBuffer, vcAndProvideBuffer]))
}
sendPe4 (infoHash) {
this.setEncrypt(this._sharedSecret, infoHash)
var padDLen = randombytes(2).readUInt16BE(0) % 512
var padDBuffer = randombytes(padDLen)
var vcAndSelectBuffer = Buffer.alloc(8 + 4 + 2 + padDLen)
VC.copy(vcAndSelectBuffer)
CRYPTO_SELECT.copy(vcAndSelectBuffer, 8)
vcAndSelectBuffer.writeInt16BE(padDLen, 12) // lenD?
padDBuffer.copy(vcAndSelectBuffer, 14)
vcAndSelectBuffer = this._encryptHandshake(vcAndSelectBuffer)
this._push(vcAndSelectBuffer)
this._cryptoHandshakeDone = true
this._debug('completed crypto handshake')
}
/**

@@ -209,2 +303,4 @@ * Message: "handshake" <pstrlen><pstr><reserved><info_hash><peer_id>

this._infoHash = infoHashBuffer
if (infoHashBuffer.length !== 20 || peerIdBuffer.length !== 20) {

@@ -398,2 +494,63 @@ throw new Error('infoHash and peerId MUST have length 20')

/**
* Sets the encryption method for this wire, as per PSE/ME specification
*
* @param {string} sharedSecret: A hex-encoded string, which is the shared secret agreed
* upon from DH key exchange
* @returns boolean, true if encryption setting succeeds, false if it fails.
*/
setEncrypt (sharedSecret, infoHash) {
var encryptKey
var decryptKey
var encryptKeyBuf
var encryptKeyIntArray
var decryptKeyBuf
var decryptKeyIntArray
switch (this.type) {
case 'tcpIncoming':
encryptKey = sha1.sync(Buffer.from(this._utfToHex('keyB') + sharedSecret + infoHash, 'hex'))
decryptKey = sha1.sync(Buffer.from(this._utfToHex('keyA') + sharedSecret + infoHash, 'hex'))
encryptKeyBuf = Buffer.from(encryptKey, 'hex')
encryptKeyIntArray = []
for (const value of encryptKeyBuf.values()) {
encryptKeyIntArray.push(value)
}
decryptKeyBuf = Buffer.from(decryptKey, 'hex')
decryptKeyIntArray = []
for (const value of decryptKeyBuf.values()) {
decryptKeyIntArray.push(value)
}
this._encryptGenerator = new RC4(encryptKeyIntArray)
this._decryptGenerator = new RC4(decryptKeyIntArray)
break
case 'tcpOutgoing':
encryptKey = sha1.sync(Buffer.from(this._utfToHex('keyA') + sharedSecret + infoHash, 'hex'))
decryptKey = sha1.sync(Buffer.from(this._utfToHex('keyB') + sharedSecret + infoHash, 'hex'))
encryptKeyBuf = Buffer.from(encryptKey, 'hex')
encryptKeyIntArray = []
for (const value of encryptKeyBuf.values()) {
encryptKeyIntArray.push(value)
}
decryptKeyBuf = Buffer.from(decryptKey, 'hex')
decryptKeyIntArray = []
for (const value of decryptKeyBuf.values()) {
decryptKeyIntArray.push(value)
}
this._encryptGenerator = new RC4(encryptKeyIntArray)
this._decryptGenerator = new RC4(decryptKeyIntArray)
break
default:
return false
}
// Discard the first 1024 bytes, as per MSE/PE implementation
for (var i = 0; i < 1024; i++) {
this._encryptGenerator.randomByte()
this._decryptGenerator.randomByte()
}
this._setGenerators = true
return true
}
/**
* Duplex stream method. Called whenever the remote peer stream wants data. No-op

@@ -423,2 +580,5 @@ * since we'll just push data whenever we get it.

if (this._finished) return
if (this._encryptionMethod === 2 && this._cryptoHandshakeDone) {
data = this._encrypt(data)
}
return this.push(data)

@@ -436,2 +596,52 @@ }

_onPe1 (pubKeyBuffer) {
this._peerPubKey = pubKeyBuffer.toString('hex')
this._sharedSecret = this._dh.computeSecret(this._peerPubKey, 'hex', 'hex')
this.emit('pe1')
}
_onPe2 (pubKeyBuffer) {
this._peerPubKey = pubKeyBuffer.toString('hex')
this._sharedSecret = this._dh.computeSecret(this._peerPubKey, 'hex', 'hex')
this.emit('pe2')
}
_onPe3 (hashesXorBuffer) {
var hash3 = sha1.sync(Buffer.from(this._utfToHex('req3') + this._sharedSecret, 'hex'))
var sKeyHash = xor(hashesXorBuffer, Buffer.from(hash3, 'hex')).toString('hex')
this.emit('pe3', sKeyHash)
}
_onPe3Encrypted (vcBuffer, peerProvideBuffer, padCBuffer, iaBuffer) {
var self = this
if (!vcBuffer.equals(VC)) {
self._debug('Error: verification constant did not match')
self.destroy()
return
}
for (const provideByte of peerProvideBuffer.values()) {
if (provideByte !== 0) {
this._peerCryptoProvide.push(provideByte)
}
}
if (this._peerCryptoProvide.includes(2)) {
this._encryptionMethod = 2
} else {
self._debug('Error: RC4 encryption method not provided by peer')
self.destroy()
}
}
_onPe4 (peerSelectBuffer) {
this._encryptionMethod = peerSelectBuffer.readUInt8(3)
if (!CRYPTO_PROVIDE.includes(this._encryptionMethod)) {
this._debug('Error: peer selected invalid crypto method')
this.destroy()
}
this._cryptoHandshakeDone = true
this._debug('crypto handshake done')
this.emit('pe4')
}
_onHandshake (infoHashBuffer, peerIdBuffer, extensions) {

@@ -592,14 +802,36 @@ const infoHash = infoHashBuffer.toString('hex')

_write (data, encoding, cb) {
if (this._encryptionMethod === 2 && this._cryptoHandshakeDone) {
data = this._decrypt(data)
}
this._bufferSize += data.length
this._buffer.push(data)
if (this._buffer.length > 1) {
this._buffer = [Buffer.concat(this._buffer, this._bufferSize)]
}
// now this._buffer is an array containing a single Buffer
if (this._cryptoSyncPattern) {
const index = this._buffer[0].indexOf(this._cryptoSyncPattern)
if (index !== -1) {
this._buffer[0] = this._buffer[0].slice(index + this._cryptoSyncPattern.length)
this._bufferSize -= (index + this._cryptoSyncPattern.length)
this._cryptoSyncPattern = null
} else if (this._bufferSize + data.length > this._waitMaxBytes + this._cryptoSyncPattern.length) {
this._debug('Error: could not resynchronize')
this.destroy()
return
}
}
while (this._bufferSize >= this._parserSize) {
const buffer = (this._buffer.length === 1)
? this._buffer[0]
: Buffer.concat(this._buffer, this._bufferSize)
this._bufferSize -= this._parserSize
this._buffer = this._bufferSize
? [buffer.slice(this._parserSize)]
: []
this._parser(buffer.slice(0, this._parserSize))
while (this._bufferSize >= this._parserSize && !this._cryptoSyncPattern) {
if (this._parserSize === 0) {
this._parser(Buffer.from([]))
} else {
var buffer = this._buffer[0]
// console.log('buffer:', this._buffer)
this._bufferSize -= this._parserSize
this._buffer = this._bufferSize
? [buffer.slice(this._parserSize)]
: []
this._parser(buffer.slice(0, this._parserSize))
}
}

@@ -653,2 +885,7 @@

_parseUntil (pattern, maxBytes) {
this._cryptoSyncPattern = pattern
this._waitMaxBytes = maxBytes
}
/**

@@ -716,22 +953,122 @@ * Handle the first 4 bytes of a message, to determine the length of bytes that must be

_determineHandshakeType () {
var self = this
self._parse(1, function (pstrLenBuffer) {
var pstrlen = pstrLenBuffer.readUInt8(0)
if (pstrlen === 19) {
self._parse(pstrlen + 48, self._onHandshakeBuffer)
} else {
this._parsePe1(pstrLenBuffer)
}
})
}
_parsePe1 (pubKeyPrefix) {
var self = this
self._parse(95, function (pubKeySuffix) {
self._onPe1(Buffer.concat([pubKeyPrefix, pubKeySuffix]))
self._parsePe3()
})
}
_parsePe2 () {
var self = this
self._parse(96, function (pubKey) {
self._onPe2(pubKey)
while (!self._setGenerators) {
// Wait until generators have been set
}
self._parsePe4()
})
}
// Handles the unencrypted portion of step 4
_parsePe3 () {
var self = this
var hash1Buffer = Buffer.from(sha1.sync(Buffer.from(this._utfToHex('req1') + this._sharedSecret, 'hex')), 'hex')
// synchronize on HASH('req1', S)
self._parseUntil(hash1Buffer, 512)
self._parse(20, function (buffer) {
self._onPe3(buffer)
while (!self._setGenerators) {
// Wait until generators have been set
}
self._parsePe3Encrypted()
})
}
_parsePe3Encrypted () {
var self = this
self._parse(14, function (buffer) {
var vcBuffer = self._decryptHandshake(buffer.slice(0, 8))
var peerProvideBuffer = self._decryptHandshake(buffer.slice(8, 12))
var padCLen = self._decryptHandshake(buffer.slice(12, 14)).readUInt16BE(0)
self._parse(padCLen, function (padCBuffer) {
padCBuffer = self._decryptHandshake(padCBuffer)
self._parse(2, function (iaLenBuf) {
var iaLen = self._decryptHandshake(iaLenBuf).readUInt16BE(0)
self._parse(iaLen, function (iaBuffer) {
iaBuffer = self._decryptHandshake(iaBuffer)
self._onPe3Encrypted(vcBuffer, peerProvideBuffer, padCBuffer, iaBuffer)
var pstrlen = iaLen ? iaBuffer.readUInt8(0) : null
var protocol = iaLen ? iaBuffer.slice(1, 20) : null
if (pstrlen === 19 && protocol.toString() === 'BitTorrent protocol') {
self._onHandshakeBuffer(iaBuffer.slice(1))
} else {
self._parseHandshake()
}
})
})
})
})
}
_parsePe4 () {
var self = this
// synchronize on ENCRYPT(VC).
// since we encrypt using bitwise xor, decryption and encryption are the same operation.
// calling _decryptHandshake here advances the decrypt generator keystream forward 8 bytes
var vcBufferEncrypted = self._decryptHandshake(VC)
self._parseUntil(vcBufferEncrypted, 512)
self._parse(6, function (buffer) {
var peerSelectBuffer = self._decryptHandshake(buffer.slice(0, 4))
var padDLen = self._decryptHandshake(buffer.slice(4, 6)).readUInt16BE(0)
self._parse(padDLen, function (padDBuf) {
self._decryptHandshake(padDBuf)
self._onPe4(peerSelectBuffer)
self._parseHandshake(null)
})
})
}
/**
* Reads the handshake as specified by the bittorrent wire protocol.
*/
_parseHandshake () {
this._parse(1, buffer => {
const pstrlen = buffer.readUInt8(0)
this._parse(pstrlen + 48, handshake => {
const protocol = handshake.slice(0, pstrlen)
if (protocol.toString() !== 'BitTorrent protocol') {
this._debug('Error: wire not speaking BitTorrent protocol (%s)', protocol.toString())
this.end()
return
}
handshake = handshake.slice(pstrlen)
this._onHandshake(handshake.slice(8, 28), handshake.slice(28, 48), {
dht: !!(handshake[7] & 0x01), // see bep_0005
extended: !!(handshake[5] & 0x10) // see bep_0010
})
this._parse(4, this._onMessageLength)
})
if (pstrlen !== 19) {
this._debug('Error: wire not speaking BitTorrent protocol (%s)', pstrlen.toString())
this.end()
return
}
this._parse(pstrlen + 48, this._onHandshakeBuffer)
})
}
_onHandshakeBuffer (handshake) {
var protocol = handshake.slice(0, 19)
if (protocol.toString() !== 'BitTorrent protocol') {
this._debug('Error: wire not speaking BitTorrent protocol (%s)', protocol.toString())
this.end()
return
}
handshake = handshake.slice(19)
this._onHandshake(handshake.slice(8, 28), handshake.slice(28, 48), {
dht: !!(handshake[7] & 0x01), // see bep_0005
extended: !!(handshake[5] & 0x10) // see bep_0010
})
this._parse(4, this._onMessageLength)
}
_onFinish () {

@@ -771,4 +1108,66 @@ this._finished = true

}
_encryptHandshake (buf) {
var crypt = Buffer.from(buf)
if (!this._encryptGenerator) {
this._debug('Warning: Encrypting without any generator')
return crypt
}
for (var i = 0; i < buf.length; i++) {
var keystream = this._encryptGenerator.randomByte()
crypt[i] = crypt[i] ^ keystream
}
return crypt
}
_encrypt (buf) {
var crypt = Buffer.from(buf)
if (!this._encryptGenerator || this._encryptionMethod !== 2) {
return crypt
}
for (var i = 0; i < buf.length; i++) {
var keystream = this._encryptGenerator.randomByte()
crypt[i] = crypt[i] ^ keystream
}
return crypt
}
_decryptHandshake (buf) {
var decrypt = Buffer.from(buf)
if (!this._decryptGenerator) {
this._debug('Warning: Decrypting without any generator')
return decrypt
}
for (var i = 0; i < buf.length; i++) {
var keystream = this._decryptGenerator.randomByte()
decrypt[i] = decrypt[i] ^ keystream
}
return decrypt
}
_decrypt (buf) {
var decrypt = Buffer.from(buf)
if (!this._decryptGenerator || this._encryptionMethod !== 2) {
return decrypt
}
for (var i = 0; i < buf.length; i++) {
var keystream = this._decryptGenerator.randomByte()
decrypt[i] = decrypt[i] ^ keystream
}
return decrypt
}
_utfToHex (str) {
return Buffer.from(str, 'utf8').toString('hex')
}
}
module.exports = Wire

5

package.json
{
"name": "bittorrent-protocol",
"description": "Simple, robust, BitTorrent peer wire protocol implementation",
"version": "3.3.2",
"version": "3.4.0",
"author": {

@@ -16,5 +16,8 @@ "name": "WebTorrent LLC",

"bitfield": "^4.0.0",
"buffer-xor": "^2.0.2",
"debug": "^4.3.1",
"randombytes": "^2.1.0",
"rc4": "^0.1.5",
"readable-stream": "^3.6.0",
"simple-sha1": "^3.0.0",
"speedometer": "^1.1.0",

@@ -21,0 +24,0 @@ "unordered-array-remove": "^1.0.2"

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc