Skip to content

Commit

Permalink
feat(p2p): encryption & authentication (#756)
Browse files Browse the repository at this point in the history
* revoke connection retries

* handshake crypto test

* interactive handshake (WIP)

* remove connect retryConnecting option from cli/grpc

* type fix

* new packets

* some fixes & cleanup

* add wire protocol messages framer

* remove redundant getnodes packet sending

* encryption, testing

* * HelloRequest -> SessionInit, HelloResponse -> SessionAck
* each communication direction to have it’s own encryption key
* inbound peer to use an ephemeral key for ECDH

* authentication msg

* authentication

* error handling

* comment text fix

* penalize for invalid auth

* remove raw traffic logging

* specify peer.wait return type

* nodeState -> ownNodeState

* use arrow functions

* enabling in-encryption synchronously via callback

* test update

* lint

* restore verifyReachability call

* packet checksum as a number

* remove checksum and network magic value from encrypted msg payload header

* crypto tests update

* crypto tests update

* remove timeout
  • Loading branch information
moshababo authored Jan 23, 2019
1 parent 540d8a2 commit 067378f
Show file tree
Hide file tree
Showing 43 changed files with 2,519 additions and 1,306 deletions.
2 changes: 1 addition & 1 deletion lib/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { exists, mkdir, readFile } from './utils/fsUtils';
import { LndClientConfig } from './lndclient/LndClient';
import { RaidenClientConfig } from './raidenclient/RaidenClient';
import { Level } from './Logger';
import { Network } from './types/enums';
import { Network, NetworkMagic } from './types/enums';
import { PoolConfig } from './types/p2p';

class Config {
Expand Down
6 changes: 3 additions & 3 deletions lib/Xud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Xud extends EventEmitter {
raidenAddress: this.raidenClient.address,
lndbtcPubKey: this.lndbtcClient.pubKey,
lndltcPubKey: this.lndltcClient.pubKey,
});
}, this.nodeKey);

this.service = new Service(loggers.global, {
version,
Expand Down Expand Up @@ -164,12 +164,12 @@ class Xud extends EventEmitter {
private bind = () => {
this.lndbtcClient.on('connectionVerified', (newPubKey) => {
if (newPubKey) {
this.pool.updateHandshake({ lndbtcPubKey: newPubKey });
this.pool.updateNodeState({ lndbtcPubKey: newPubKey });
}
});
this.lndltcClient.on('connectionVerified', (newPubKey) => {
if (newPubKey) {
this.pool.updateHandshake({ lndltcPubKey: newPubKey });
this.pool.updateNodeState({ lndltcPubKey: newPubKey });
}
});
}
Expand Down
4 changes: 4 additions & 0 deletions lib/nodekey/NodeKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class NodeKey {
return this.pubKeyStr;
}

public get nodePrivKey(): Buffer {
return this.privKey;
}

/**
* Generates a random NodeKey.
*/
Expand Down
4 changes: 2 additions & 2 deletions lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class OrderBook extends EventEmitter {
this.tradingPairs.set(pairInstance.id, new TradingPair(this.logger, pairInstance.id, this.nomatching));

if (this.pool) {
this.pool.updateHandshake({ pairs: this.pairIds });
this.pool.updateNodeState({ pairs: this.pairIds });
}
return pairInstance;
}
Expand Down Expand Up @@ -234,7 +234,7 @@ class OrderBook extends EventEmitter {
this.tradingPairs.delete(pairId);

if (this.pool) {
this.pool.updateHandshake({ pairs: this.pairIds });
this.pool.updateNodeState({ pairs: this.pairIds });
}
return pair.destroy();
}
Expand Down
183 changes: 183 additions & 0 deletions lib/p2p/Framer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import assert from 'assert';
import crypto from 'crypto';
import Network from './Network';
import Packet from './packets/Packet';
import errors from './errors';

type WireMsgHeader = {
magic?: number,
type: number,
length: number,
checksum?: number,
};

type WireMsg = {
header: WireMsgHeader,
packet: Buffer;
};

/** Wire protocol msg framer */
class Framer {
public static readonly MSG_HEADER_LENGTH = 16;
public static readonly ENCRYPTED_MSG_HEADER_LENGTH = 4;
public static readonly ENCRYPTED_MSG_PAYLOAD_HEADER_LENGTH = 8;
public static readonly ENCRYPTION_KEY_LENGTH = 32;
public static readonly ENCRYPTION_IV_LENGTH = 16;

constructor(private network: Network) {
}

/**
* Frame a packet with a header to be used as a wire msg
*/
public frame = (packet: Packet, encryptionKey?: Buffer): Buffer => {
const packetRaw = packet.toRaw();

if (encryptionKey) {
const msg = Buffer.allocUnsafe(Framer.ENCRYPTED_MSG_PAYLOAD_HEADER_LENGTH + packetRaw.length);

// length
msg.writeUInt32LE(packetRaw.length, 0, true);

// type
msg.writeUInt32LE(packet.type, 4, true);

// packet
packetRaw.copy(msg, 8);

const ciphertext = this.encrypt(msg, encryptionKey);
const encryptedMsg = Buffer.allocUnsafe(Framer.ENCRYPTED_MSG_HEADER_LENGTH + ciphertext.length);

// length
encryptedMsg.writeUInt32LE(ciphertext.length, 0, true);

// ciphertext
ciphertext.copy(encryptedMsg, 4);

return encryptedMsg;
} else {
const msg = Buffer.allocUnsafe(Framer.MSG_HEADER_LENGTH + packetRaw.length);

// network magic value
msg.writeUInt32LE(this.network.magic, 0, true);

// length
msg.writeUInt32LE(packetRaw.length, 4, true);

// type
msg.writeUInt32LE(packet.type, 8, true);

// checksum
msg.writeUInt32LE(packet.checksum(), 12);

// payload
packetRaw.copy(msg, 16);

return msg;
}
}

/**
* Unframe a wire msg or an encrypted wire msg
*/
public unframe = (msg: Buffer, encryptionKey?: Buffer): WireMsg => {
let wireMsg: WireMsg;
if (encryptionKey) {
const length = msg.readUInt32LE(0, true);
const ciphertext = msg.slice(Framer.ENCRYPTED_MSG_HEADER_LENGTH);

if (length !== ciphertext.length) {
throw errors.FRAMER_INVALID_MSG_LENGTH(length, ciphertext.length);
}

const decryptedMsg = this.decrypt(ciphertext, encryptionKey);

wireMsg = {
header: this.parseHeader(decryptedMsg, true),
packet: decryptedMsg.slice(Framer.ENCRYPTED_MSG_PAYLOAD_HEADER_LENGTH),
};
} else {
wireMsg = {
header: this.parseHeader(msg, false),
packet: msg.slice(Framer.MSG_HEADER_LENGTH),
};
}

if (wireMsg.header.length !== wireMsg.packet.length) {
throw errors.FRAMER_INVALID_MSG_LENGTH(wireMsg.header.length, wireMsg.packet.length);
}

return wireMsg;
}

/**
* Parse the length of a wire msg or an encrypted wire msg
*/
public parseLength = (data: Buffer, encrypted: boolean): number => {
const value = data.readUInt32LE(0, true);

if (encrypted) {
if (value === this.network.magic) {
throw errors.FRAMER_MSG_NOT_ENCRYPTED;
}
return value;
}

if (value !== this.network.magic) {
throw errors.FRAMER_INVALID_NETWORK_MAGIC_VALUE;
}

return data.readUInt32LE(4, true);
}

/**
* Parse the header of a wire msg or an encrypted wire msg payload
*/
public parseHeader = (msg: Buffer, encrypted: boolean): WireMsgHeader => {
if (encrypted) {
assert(msg.length >= Framer.ENCRYPTED_MSG_PAYLOAD_HEADER_LENGTH, `invalid msg header length: data is missing`);

// length
const length = msg.readUInt32LE(0, true);

// type
const type = msg.readUInt32LE(4, true);

return { length, type };
} else {
assert(msg.length >= Framer.MSG_HEADER_LENGTH, `invalid msg header length: data is missing`);

// network magic value
const magic = msg.readUInt32LE(0, true);

// length
const length = msg.readUInt32LE(4, true);

// type
const type = msg.readUInt32LE(8, true);

// checksum
const checksum = msg.readUInt32LE(12, true);

return { magic, type, length, checksum };
}
}

public encrypt = (plaintext: Buffer, key: Buffer): Buffer => {
const iv = crypto.randomBytes(Framer.ENCRYPTION_IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

return Buffer.concat([iv, cipher.update(plaintext), cipher.final()]);
}

public decrypt = (ciphertext: Buffer, key: Buffer): Buffer => {
const iv = ciphertext.slice(0, Framer.ENCRYPTION_IV_LENGTH);
const encrypted = ciphertext.slice(Framer.ENCRYPTION_IV_LENGTH);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);

return Buffer.concat([decipher.update(encrypted), decipher.final()]);
}
}

export default Framer;
export { WireMsgHeader };
7 changes: 7 additions & 0 deletions lib/p2p/Network.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

class Network {
constructor(public magic: number) {
}
}

export default Network;
6 changes: 2 additions & 4 deletions lib/p2p/NodeList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@ export const reputationEventWeight = {
[ReputationEvent.PacketTimeout]: -1,
[ReputationEvent.SwapFailure]: -10,
[ReputationEvent.SwapSuccess]: 1,
[ReputationEvent.InvalidPacket]: -10,
[ReputationEvent.UnknownPacketType]: -20,
[ReputationEvent.PacketDataIntegrityError]: -20,
[ReputationEvent.MaxParserBufferSizeExceeded]: -20,
[ReputationEvent.WireProtocolErr]: -5,
[ReputationEvent.InvalidAuth]: -20,
};

// TODO: inform node about getting banned
Expand Down
Loading

0 comments on commit 067378f

Please sign in to comment.