diff --git a/src/fernet.js b/src/fernet.js index 0932ee2..3e5a884 100644 --- a/src/fernet.js +++ b/src/fernet.js @@ -10,12 +10,19 @@ class Fernet { throw new Error("Token key cannot be null"); } - if (key.length !== 32) { - throw new Error(`Token key must be 32 bytes, not ${key.length}`); + if (key.length === 32) { + // AES-128-CBC mode + this.mode = 'aes-128-cbc'; + this._signing_key = key.slice(0, 16); + this._encryption_key = key.slice(16); + } else if (key.length === 64) { + // AES-256-CBC mode (matches Python's Link.MODE_AES256_CBC) + this.mode = 'aes-256-cbc'; + this._signing_key = key.slice(0, 32); + this._encryption_key = key.slice(32); + } else { + throw new Error(`Token key must be 32 or 64 bytes, not ${key.length}`); } - - this._signing_key = key.slice(0, 16); - this._encryption_key = key.slice(16); } static generateKey() { @@ -46,7 +53,7 @@ class Fernet { const iv = crypto.randomBytes(16); const paddedData = PKCS7.pad(data); - const cipher = crypto.createCipheriv('aes-128-cbc', this._encryption_key, iv); + const cipher = crypto.createCipheriv(this.mode, this._encryption_key, iv); cipher.setAutoPadding(false); // disable automatic pkcs7 padding by crypto lib let ciphertext = cipher.update(paddedData); ciphertext = Buffer.concat([ciphertext, cipher.final()]); @@ -73,7 +80,7 @@ class Fernet { const iv = token.slice(0, 16); const ciphertext = token.slice(16, -32); - const decipher = crypto.createDecipheriv('aes-128-cbc', this._encryption_key, iv); + const decipher = crypto.createDecipheriv(this.mode, this._encryption_key, iv); decipher.setAutoPadding(false); // disable automatic pkcs7 padding by crypto lib let plaintext = decipher.update(ciphertext); plaintext = Buffer.concat([plaintext, decipher.final()]); diff --git a/src/interfaces/interface.js b/src/interfaces/interface.js index 68c72d9..972ef5e 100644 --- a/src/interfaces/interface.js +++ b/src/interfaces/interface.js @@ -6,6 +6,12 @@ class Interface { this.rns = null; this.name = name; this.hash = this.getHash(); + + // Interface statistics (match Python implementation) + this.rxb = 0; // Received bytes counter + this.txb = 0; // Transmitted bytes counter + this.online = false; // Interface connection status + this.OUT = false; // Whether interface can send data } setReticulumInstance(rns) { diff --git a/src/interfaces/websocket_client_interface.js b/src/interfaces/websocket_client_interface.js index 78500a4..33198ac 100644 --- a/src/interfaces/websocket_client_interface.js +++ b/src/interfaces/websocket_client_interface.js @@ -25,6 +25,8 @@ class WebsocketClientInterface extends Interface { // connect to server this.websocket.addEventListener("open", () => { console.log(`Connected to: ${this.name} [${this.url}]`); + this.online = true; + this.OUT = true; }); // handle received data @@ -56,6 +58,8 @@ class WebsocketClientInterface extends Interface { // connect to server this.websocket.on("open", () => { console.log(`Connected to: ${this.name} [${this.url}]`); + this.online = true; + this.OUT = true; }); // handle received data @@ -82,6 +86,8 @@ class WebsocketClientInterface extends Interface { onSocketClose() { console.error('Connection Closed'); + this.online = false; + this.OUT = false; // auto reconnect setTimeout(() => { @@ -91,7 +97,15 @@ class WebsocketClientInterface extends Interface { } sendData(data) { + // Check if WebSocket is ready (state 1 = OPEN) + if (!this.websocket || this.websocket.readyState !== 1) { + return; + } + this.websocket.send(data); + + // Increment TX byte counter (match Python implementation) + this.txb += data.length; } onDataReceived(data) { @@ -106,6 +120,9 @@ class WebsocketClientInterface extends Interface { return; } + // Increment RX byte counter (match Python implementation) + this.rxb += data.length; + // parse packet from bytes const packet = Packet.fromBytes(data); diff --git a/src/link.js b/src/link.js index 5a84270..edf275b 100644 --- a/src/link.js +++ b/src/link.js @@ -285,8 +285,8 @@ class Link extends EventEmitter { // compute shared key this.sharedKey = Buffer.from(x25519.getSharedSecret(this.privateKeyBytes, this.peerPublicKeyBytes)); - // create derived key - this.derivedKey = Cryptography.hkdf(32, this.sharedKey, this.hash); + // create derived key (64 bytes for AES256 mode to match Python's Link.MODE_AES256_CBC) + this.derivedKey = Cryptography.hkdf(64, this.sharedKey, this.hash); } @@ -335,8 +335,15 @@ class Link extends EventEmitter { } decrypt(data) { - const fernet = new Fernet(this.derivedKey); - return fernet.decrypt(data); + try { + if (!this.derivedKey) { + return null; + } + const fernet = new Fernet(this.derivedKey); + return fernet.decrypt(data); + } catch (e) { + return null; + } } sign(data) { @@ -386,6 +393,11 @@ class Link extends EventEmitter { // decrypt packet data const plaintext = this.decrypt(packet.data); + // check if decryption failed + if (!plaintext) { + return; + } + // fire event this.emit("packet", { packet: packet, @@ -406,7 +418,7 @@ class Link extends EventEmitter { // decrypt link id from packet data and do nothing if it doesn't match this link const linkIdToClose = this.decrypt(packet.data); - if(!this.hash.equals(linkIdToClose)){ + if(!linkIdToClose || !this.hash.equals(linkIdToClose)){ return; }