Connection: Implement sequence numbering system to avoid replay attacks
This commit is contained in:
parent
a01792d2d5
commit
3f26696056
1 changed files with 25 additions and 5 deletions
|
@ -45,6 +45,9 @@ class Connection extends EventEmitter {
|
||||||
this.rekey_interval = this.rekey_interval_base + crypto.randomInt(0, 15 * 60 * 1000);
|
this.rekey_interval = this.rekey_interval_base + crypto.randomInt(0, 15 * 60 * 1000);
|
||||||
this.rekey_in_progress = false;
|
this.rekey_in_progress = false;
|
||||||
|
|
||||||
|
this.sequence_count_receive = 0;
|
||||||
|
this.sequence_count_send = 0;
|
||||||
|
|
||||||
this.session_key = Buffer.from(secret_join, "base64");
|
this.session_key = Buffer.from(secret_join, "base64");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,8 +96,13 @@ class Connection extends EventEmitter {
|
||||||
this.session_key = await rekey(this, this.session_key);
|
this.session_key = await rekey(this, this.session_key);
|
||||||
this.rekey_interval = this.rekey_interval_base + crypto.randomInt(0, 15 * 60 * 1000);
|
this.rekey_interval = this.rekey_interval_base + crypto.randomInt(0, 15 * 60 * 1000);
|
||||||
this.rekey_last = new Date();
|
this.rekey_last = new Date();
|
||||||
|
// Also reset the sequence counters. This will help avoid integer overflow issues - however unlikely they may be
|
||||||
|
this.sequence_count_send = 0;
|
||||||
|
this.sequence_count_receive = 0;
|
||||||
/**
|
/**
|
||||||
* The session key has been re-exchanged.
|
* The session key has been re-exchanged.
|
||||||
|
* This event is fired with the rekeying process
|
||||||
|
* is complete.
|
||||||
* @event Connection#rekey
|
* @event Connection#rekey
|
||||||
* @type {void}
|
* @type {void}
|
||||||
*/
|
*/
|
||||||
|
@ -144,9 +152,21 @@ class Connection extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handle_message(msg_text) {
|
async handle_message(msg_text) {
|
||||||
|
// If this JSON.parse() call fails, we kill the connection 'cause the
|
||||||
|
// catch part of the above try..catch will trigger in this.handle_frame.
|
||||||
|
// This is very important, because if it didn't kill the connection
|
||||||
|
// we would no doubt be open to all sorts of unpleasant attacks
|
||||||
|
// given that we wouldn't have a chance to check the sequence number
|
||||||
|
// (see below).
|
||||||
const msg = JSON.parse(msg_text);
|
const msg = JSON.parse(msg_text);
|
||||||
|
|
||||||
l.debug(`RECEIVE:${msg.event}`, msg.message);
|
l.debug(`RECEIVE ${msg.sequence}:${msg.event}`, msg.message);
|
||||||
|
|
||||||
|
if(msg.sequence !== this.sequence_count_receive) {
|
||||||
|
l.warn(`Killing connection due to invalid sequence number in received message: expected ${this.sequence_count_receive}, but got ${msg.sequence}.`);
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
this.sequence_count_receive++;
|
||||||
|
|
||||||
if(msg.event == "rekey" && !this.rekey_in_progress) {
|
if(msg.event == "rekey" && !this.rekey_in_progress) {
|
||||||
// Set and forget here
|
// Set and forget here
|
||||||
|
@ -178,12 +198,12 @@ class Connection extends EventEmitter {
|
||||||
/*
|
/*
|
||||||
TODO: Consider anonymous TLS, with jpake for mututal authentication
|
TODO: Consider anonymous TLS, with jpake for mututal authentication
|
||||||
TODO: Consider https://devdocs.io/node/crypto#crypto.createCipheriv() - which lets us use any openssl ciphers we like - e.g. ChaCha20-Poly1305
|
TODO: Consider https://devdocs.io/node/crypto#crypto.createCipheriv() - which lets us use any openssl ciphers we like - e.g. ChaCha20-Poly1305
|
||||||
TODO: We're currently vulnerable to a replay attack. We need to mitigate this somehow - probably by maintaining a sequence number. Instead of sending the sequence number though we should instead compute a MAC that also includes the message length and a bunch of other things etc. Of course, we will also need to make sure we don't fall afoul of mac-then-encrypt, encrypt-then-mac, etc issues...
|
Note here that we do *not* need another manual MAC to make this
|
||||||
Ref https://www.rfc-editor.org/rfc/rfc4346#appendix-F.2 for the sequence counter in TLS
|
authenticated, as tweetnacl's secretbox uses xsalsa20-poly1305,
|
||||||
We do *not* need another manual MAC, as tweetnacl's secretbox uses xsalsa20-poly1305, which is an *authenticated* encryption algorithm. Thus, all we need do is prepend the plaintext with a sequence number.
|
which is an *authenticated* encryption algorithm.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let payload = JSON.stringify({ event, message });
|
let payload = JSON.stringify({ event, message, sequence: this.sequence_count_send++ });
|
||||||
payload = encrypt_bytes(
|
payload = encrypt_bytes(
|
||||||
this.session_key,
|
this.session_key,
|
||||||
Buffer.from(payload, "utf-8")
|
Buffer.from(payload, "utf-8")
|
||||||
|
|
Loading…
Reference in a new issue