474 lines
17 KiB
JavaScript
474 lines
17 KiB
JavaScript
|
|
/*
|
||
|
|
Copyright (c) 2014 IOnU Security Inc. All rights reserved
|
||
|
|
Created April 2014 by Kendrick Webster
|
||
|
|
|
||
|
|
K2Proxy/k2.js - interface to K2Client and K2Daemon,
|
||
|
|
implementation of proxy functionality
|
||
|
|
*/
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
var sprintf = require("sprintf-js").sprintf,
|
||
|
|
hash = require("./hash"),
|
||
|
|
k2_map = require("./k2_map"),
|
||
|
|
inum_map = require("./inum_map"),
|
||
|
|
k2_udp = require("./k2_udp"),
|
||
|
|
router = require("./router");
|
||
|
|
|
||
|
|
function trace(m) { // comment or un-comment as needed for debugging:
|
||
|
|
// console.log(m);
|
||
|
|
}
|
||
|
|
function trace_info(m) { // comment or un-comment as needed for debugging:
|
||
|
|
console.log(m);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/*
|
||
|
|
Poll messages (GET /recv/<message>, base64) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 sequence number (see init message)
|
||
|
|
n client URN (string, no terminator, implicit length)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
All fields after the nonce are encrypted using an auto-hashing stream cipher
|
||
|
|
with a hard-coded key. The decrypted MIC will match only for properly encrypted
|
||
|
|
packets. Spurious packets fail the MIC check. This is used to increase the
|
||
|
|
difficulty of DOS attacks. The sequence number is included to prevent replay
|
||
|
|
DOS attacks.
|
||
|
|
|
||
|
|
The response to a valid poll message is a text document containing the next
|
||
|
|
received packet for the client URN, base64 encoded, followed by a period '.'
|
||
|
|
alone on a line. In case no packet arrives within the poll timeout interval,
|
||
|
|
the response is "timeout".
|
||
|
|
|
||
|
|
Invalid messages result in a 404 response (delayed to minimize DOS impact).
|
||
|
|
*/
|
||
|
|
/*
|
||
|
|
Send messages (POST to /send) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 sequence number (see init message)
|
||
|
|
n K2 UDP packet (containing client URN)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
Encryption is done in the same manner (and for the same reasons) as for poll
|
||
|
|
messages. The sequence number in send messages is independent from the
|
||
|
|
sequence number in poll messages.
|
||
|
|
|
||
|
|
The K2 UDP packet must be of a type that contains a client URN. This is the
|
||
|
|
case for all K2 UDP packet types that are sent from a client to the daemon.
|
||
|
|
|
||
|
|
The response to a valid send message is a text document containing a single
|
||
|
|
line with the text "OK".
|
||
|
|
|
||
|
|
Invalid messages result in a 404 response (delayed to minimize DOS impact).
|
||
|
|
*/
|
||
|
|
/*
|
||
|
|
Init messages (GET /init/<message>, base64) consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
n client URN (string, no terminator, implicit length)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
The response to an init message is a sequence number message:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
9 nonce (random initialization vector)
|
||
|
|
4 poll sequence number (randomly initialized)
|
||
|
|
4 send sequence number (randomly initialized)
|
||
|
|
9 message integrity code (MIC)
|
||
|
|
|
||
|
|
The sequence numbers are randomly-generated and stored as part of a session's
|
||
|
|
state (associated with the client URN) when a session is initially established.
|
||
|
|
Upon receiving each poll or send message, the corresponding sequence number is
|
||
|
|
checked, and, if it matches, incremented modulo 2^32.
|
||
|
|
|
||
|
|
A 'session' remains active as long as there is traffic (i.e. K2IPC hearbeat
|
||
|
|
messages). An absence of traffic causes a session to timeout.
|
||
|
|
|
||
|
|
Repeated init messages return the current sequence numbers associated with the
|
||
|
|
session. The reply (to all init messages) is delayed a small amount to limit
|
||
|
|
the impact of a replay attack. Although the delay could be selectively applied
|
||
|
|
only for existing sessions, doing so would leak information about who is
|
||
|
|
currently logged in. The delay is thus chosen to be small enough to have a
|
||
|
|
minimal impact on clients while still being large enough to limit traffic and
|
||
|
|
server load in a replay DOS attack.
|
||
|
|
*/
|
||
|
|
|
||
|
|
var K2PROXY_KEY = 'ICIRY4arfFbqGz9YGZaFRvv6+RNnXrsy';
|
||
|
|
var K2PROXY_MIC = 'KyYh/7xgwUsU';
|
||
|
|
var K2PROXY_NONCE_SIZE = 9;
|
||
|
|
var K2PROXY_MIC_SIZE = 9;
|
||
|
|
var K2PROXY_SEQUENCE_NUMBER_SIZE = 4;
|
||
|
|
var K2PROXY_OVERHEAD = K2PROXY_NONCE_SIZE + K2PROXY_MIC_SIZE;
|
||
|
|
var K2PROXY_INIT_RESPONSE_SIZE = 2 * K2PROXY_SEQUENCE_NUMBER_SIZE + K2PROXY_OVERHEAD;
|
||
|
|
var MIN_BASE64_MESSAGE_LEN = (K2PROXY_OVERHEAD * 8) / 6;
|
||
|
|
var K2PROXY_KEY_MIX_CYCLES = 32;
|
||
|
|
var POLL_TIMEOUT_RESPONSE = "timeout";
|
||
|
|
var SEND_MESSAGE_RESPONSE = "OK\r\n.\r\n";
|
||
|
|
var INIT_RESPONSE_DELAY_MS = 330; // see comments for "Repeated init messages"
|
||
|
|
|
||
|
|
/*
|
||
|
|
K2 UDP packets consist of the following fields:
|
||
|
|
|
||
|
|
octets description
|
||
|
|
--------------------------------------------------------------------------
|
||
|
|
7 nonce (random initialization vector)
|
||
|
|
2 protocol version
|
||
|
|
3 client instance (for multiple clients sharing a UDP port)
|
||
|
|
1 sequence number (echoed in ACK)
|
||
|
|
1 opcode
|
||
|
|
<n> body, per opcode, NUL-terminated string
|
||
|
|
6 message integrity code (MIC)
|
||
|
|
|
||
|
|
All fields after the nonce are encrypted using an auto-hashing stream
|
||
|
|
cipher with a hard-coded key. The decrypted MIC will match only for
|
||
|
|
properly encrypted packets. Spurious packets fail the MIC check. This
|
||
|
|
'encryption' is used only for spurious packet rejection (not security).
|
||
|
|
|
||
|
|
Multi-byte fields are in network byte order (big endian).
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* packet encryption/validation key and MIC */
|
||
|
|
var K2IPC_KEY = new Buffer([
|
||
|
|
169, 208, 1, 214, 130, 153, 218, 176, 187, 115, 186, 84, 117, 25, 162, 197,
|
||
|
|
173, 73, 161, 180, 68, 224, 62, 241, 192, 96, 152, 79, 148, 239, 20, 132]);
|
||
|
|
var K2IPC_KEY_MIX_CYCLES = 32;
|
||
|
|
var K2IPC_MIC = new Buffer([17, 44, 195, 238, 248, 76]);
|
||
|
|
|
||
|
|
/* packet opcodes */
|
||
|
|
var OPCODES = {
|
||
|
|
CONNECT : {value: 0, name: "CONNECT"},
|
||
|
|
ACK_CONNECT : {value: 1, name: "ACK_CONNECT"},
|
||
|
|
NAK_CONNECT : {value: 2, name: "NAK_CONNECT"},
|
||
|
|
MESSAGE : {value: 3, name: "MESSAGE"},
|
||
|
|
ACK_MESSAGE : {value: 4, name: "ACK_MESSAGE"},
|
||
|
|
LOGOUT : {value: 5, name: "LOGOUT"},
|
||
|
|
ACK_LOGOUT : {value: 6, name: "ACK_LOGOUT"},
|
||
|
|
POLL_DB : {value: 7, name: "POLL_DB"},
|
||
|
|
ACK_POLL_DB : {value: 8, name: "ACK_POLL_DB"},
|
||
|
|
HEARTBEAT : {value: 9, name: "HEARTBEAT"},
|
||
|
|
ACK_HEARTBEAT : {value: 10, name: "ACK_HEARTBEAT"},
|
||
|
|
SUBSCRIBE_OFFICE : {value: 11, name: "SUBSCRIBE_OFFICE"},
|
||
|
|
ACK_SUBSCRIBE_OFFICE : {value: 12, name: "ACK_SUBSCRIBE_OFFICE"},
|
||
|
|
DEVICE_STATUS : {value: 13, name: "DEVICE_STATUS"},
|
||
|
|
ACK_DEVICE_STATUS : {value: 14, name: "ACK_DEVICE_STATUS"},
|
||
|
|
QUERY_STATS : {value: 15, name: "QUERY_STATS"},
|
||
|
|
ACK_QUERY_STATS : {value: 16, name: "ACK_QUERY_STATS"},
|
||
|
|
PING : {value: 17, name: "PING"},
|
||
|
|
ACK_PING : {value: 18, name: "ACK_PING"}
|
||
|
|
};
|
||
|
|
|
||
|
|
var opcode_lut = [];
|
||
|
|
for (var opcode in OPCODES) {
|
||
|
|
if (OPCODES.hasOwnProperty(opcode)) {
|
||
|
|
opcode_lut[OPCODES[opcode].value] = OPCODES[opcode];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function showK2Packet(direction, length, meta, sequence) {
|
||
|
|
trace_info(sprintf('K2 %s %-24s %-20s inum:%9u clinum:%9u len:%5u sequence:%11u',
|
||
|
|
direction, meta.urn, meta.opcode.name, meta.inum, meta.clinum, length, sequence));
|
||
|
|
}
|
||
|
|
|
||
|
|
function readInt24BE(buf, offset) {
|
||
|
|
var value = buf[offset];
|
||
|
|
value <<= 8;
|
||
|
|
value += buf[offset + 1];
|
||
|
|
value <<= 8;
|
||
|
|
value += buf[offset + 2];
|
||
|
|
return value;
|
||
|
|
}
|
||
|
|
function writeInt24BE(buf, value, offset) {
|
||
|
|
buf[offset] = (value >>> 16) & 0xFF;
|
||
|
|
buf[offset + 1] = (value >>> 8) & 0xFF;
|
||
|
|
buf[offset + 2] = value & 0xFF;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Re-encode and encrypt the cleartext K2 UDP packet in <buf> with <client_instance>
|
||
|
|
replacing the previously encoded value.
|
||
|
|
*/
|
||
|
|
function encodePacket(buf, client_instance) {
|
||
|
|
var h;
|
||
|
|
hash.getbuf(hash.rng_hash, buf, 7); // IV nonce
|
||
|
|
writeInt24BE(buf, client_instance, 9);
|
||
|
|
h = new hash.Hash();
|
||
|
|
hash.hashbuf(h, buf.slice(0, 7));
|
||
|
|
hash.hashbuf(h, K2IPC_KEY);
|
||
|
|
hash.mix(h, K2IPC_KEY_MIX_CYCLES);
|
||
|
|
hash.encrypt(h, buf.slice(7));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
decrypts K2 UDP packet in <buf>, returns <true> if packet is good (MIC validates
|
||
|
|
and protocol version is supported), sets properties of <out>:
|
||
|
|
out.urn = client URN (if contained in packet)
|
||
|
|
out.inum = client instance number used by proxy for daemon-bound packets
|
||
|
|
out.clinum = client instance number used by client
|
||
|
|
out.opcode = packet opcode (member of var OPCODES)
|
||
|
|
*/
|
||
|
|
function decodePacket(buf, out) {
|
||
|
|
var instance, opcode, i, h;
|
||
|
|
if (buf.length < 20) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
h = new hash.Hash();
|
||
|
|
hash.hashbuf(h, buf.slice(0, 7)); // IV nonce
|
||
|
|
hash.hashbuf(h, K2IPC_KEY);
|
||
|
|
hash.mix(h, K2IPC_KEY_MIX_CYCLES);
|
||
|
|
hash.decrypt(h, buf.slice(7));
|
||
|
|
if ((buf.slice(-6).toString('base64') === K2IPC_MIC.toString('base64'))
|
||
|
|
&& (buf.readInt16BE(7) === 1)) { /* protocol version 1 */
|
||
|
|
instance = readInt24BE(buf, 9);
|
||
|
|
opcode = buf[13];
|
||
|
|
out.opcode = opcode_lut[opcode];
|
||
|
|
switch (opcode) {
|
||
|
|
/*
|
||
|
|
Packets from the client to the daemon, ...
|
||
|
|
(*) The client_instance fields in the packets are set by the clients.
|
||
|
|
(*) All of these packet types contain an URN.
|
||
|
|
*/
|
||
|
|
case OPCODES.CONNECT.value:
|
||
|
|
case OPCODES.ACK_MESSAGE.value:
|
||
|
|
case OPCODES.LOGOUT.value:
|
||
|
|
case OPCODES.POLL_DB.value:
|
||
|
|
case OPCODES.HEARTBEAT.value:
|
||
|
|
case OPCODES.ACK_DEVICE_STATUS.value:
|
||
|
|
case OPCODES.QUERY_STATS.value:
|
||
|
|
case OPCODES.ACK_PING.value:
|
||
|
|
out.urn = buf.slice(14, -7).toString();
|
||
|
|
out.clinum = instance;
|
||
|
|
out.inum = inum_map.get_inum(out.urn);
|
||
|
|
if (OPCODES.CONNECT.value === opcode) {
|
||
|
|
k2_map.set_clinum(out.urn, instance);
|
||
|
|
trace('urn(' + out.urn + ') --> clinum(' + instance + ')');
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case OPCODES.SUBSCRIBE_OFFICE.value:
|
||
|
|
for (i = 14; i < buf.length; i++) {
|
||
|
|
if (44 === buf[i]) { // 44 is char code for ','
|
||
|
|
out.urn = buf.slice(14, i).toString();
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
out.clinum = instance;
|
||
|
|
out.inum = inum_map.get_inum(out.urn);
|
||
|
|
break;
|
||
|
|
|
||
|
|
/*
|
||
|
|
Packets from the daemon to the client, ...
|
||
|
|
(*) The client_instance fields in the packets are set by the proxy.
|
||
|
|
(*) Some of these packet types do not contain an URN.
|
||
|
|
*/
|
||
|
|
case OPCODES.ACK_CONNECT.value:
|
||
|
|
case OPCODES.NAK_CONNECT.value:
|
||
|
|
case OPCODES.MESSAGE.value: // lacks URN
|
||
|
|
case OPCODES.ACK_LOGOUT.value:
|
||
|
|
case OPCODES.ACK_POLL_DB.value:
|
||
|
|
case OPCODES.ACK_HEARTBEAT.value:
|
||
|
|
case OPCODES.ACK_SUBSCRIBE_OFFICE.value:
|
||
|
|
case OPCODES.DEVICE_STATUS.value: // lacks URN
|
||
|
|
case OPCODES.ACK_QUERY_STATS.value: // lacks URN
|
||
|
|
case OPCODES.PING.value:
|
||
|
|
out.urn = inum_map.get_urn(instance); // fetches URN *AND* refreshes mapping
|
||
|
|
out.inum = instance;
|
||
|
|
out.clinum = k2_map.get_clinum(out.urn);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Encrypts a proxy message
|
||
|
|
Fills in the <nonce> and <MIC> fields at the start and end of the message
|
||
|
|
*/
|
||
|
|
function encryptMessage(buf) {
|
||
|
|
var h, mic;
|
||
|
|
hash.getbuf(hash.rng_hash, buf, K2PROXY_NONCE_SIZE);
|
||
|
|
mic = new Buffer(K2PROXY_MIC, 'base64');
|
||
|
|
mic.copy(buf, buf.length - mic.length);
|
||
|
|
h = new hash.Hash();
|
||
|
|
hash.hashbuf(h, buf, K2PROXY_NONCE_SIZE);
|
||
|
|
hash.hashstr(h, K2PROXY_KEY);
|
||
|
|
hash.mix(h, K2PROXY_KEY_MIX_CYCLES);
|
||
|
|
hash.encrypt(h, buf.slice(K2PROXY_NONCE_SIZE));
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Decrypts a proxy message, returns <true> if MIC is valid
|
||
|
|
*/
|
||
|
|
function decryptMessage(buf) {
|
||
|
|
var h;
|
||
|
|
if (buf.length >= K2PROXY_OVERHEAD) {
|
||
|
|
h = new hash.Hash();
|
||
|
|
hash.hashbuf(h, buf, K2PROXY_NONCE_SIZE);
|
||
|
|
hash.hashstr(h, K2PROXY_KEY);
|
||
|
|
hash.mix(h, K2PROXY_KEY_MIX_CYCLES);
|
||
|
|
hash.decrypt(h, buf.slice(K2PROXY_NONCE_SIZE));
|
||
|
|
return (buf.slice(-K2PROXY_MIC_SIZE).toString('base64') === K2PROXY_MIC);
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* ---------------------------------------
|
||
|
|
Helpers for responding to HTTP requests
|
||
|
|
--------------------------------------- */
|
||
|
|
|
||
|
|
function finish_recv(response, packet) {
|
||
|
|
response.writeHead(200, {"Content-Type": "text/plain"});
|
||
|
|
response.write(packet.toString('base64'));
|
||
|
|
response.write('\r\n.\r\n');
|
||
|
|
response.end();
|
||
|
|
}
|
||
|
|
|
||
|
|
function poll_timeout(response) {
|
||
|
|
response.writeHead(200, {"Content-Type": "text/plain"});
|
||
|
|
response.write(POLL_TIMEOUT_RESPONSE);
|
||
|
|
response.end();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/* --------------------------------------
|
||
|
|
Handlers for events from other modules
|
||
|
|
-------------------------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handler for received UDP packet
|
||
|
|
*/
|
||
|
|
function udp_recv(packet) {
|
||
|
|
var response, meta = {};
|
||
|
|
if (decodePacket(packet, meta)) {
|
||
|
|
showK2Packet('Rx', packet.length, meta, k2_map.get_poll_sequence(meta.urn));
|
||
|
|
encodePacket(packet, meta.clinum);
|
||
|
|
response = k2_map.get_response(meta.urn);
|
||
|
|
if (typeof response === 'object') {
|
||
|
|
finish_recv(response, packet);
|
||
|
|
} else {
|
||
|
|
k2_map.put_packet(meta.urn, packet);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
k2_udp.set_receiver(udp_recv);
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handler for timed out HTTP GET '/recv' requests
|
||
|
|
*/
|
||
|
|
function recv_expired(urn, response) {
|
||
|
|
trace_info('recv expired for ' + urn);
|
||
|
|
poll_timeout(response);
|
||
|
|
}
|
||
|
|
k2_map.set_expiry_handler(recv_expired);
|
||
|
|
|
||
|
|
|
||
|
|
/* ---------------------
|
||
|
|
HTTP request handlers
|
||
|
|
--------------------- */
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handler for HTTP GET '/init'
|
||
|
|
*/
|
||
|
|
function init(response, init_message) {
|
||
|
|
var buf, urn;
|
||
|
|
buf = new Buffer(init_message, 'base64');
|
||
|
|
if (decryptMessage(buf)) {
|
||
|
|
urn = buf.slice(K2PROXY_NONCE_SIZE, -K2PROXY_MIC_SIZE).toString('utf8');
|
||
|
|
trace('init(' + urn + ')');
|
||
|
|
|
||
|
|
setTimeout(function () {
|
||
|
|
var b = new Buffer(K2PROXY_INIT_RESPONSE_SIZE), n = {};
|
||
|
|
k2_map.get_sequence_numbers(urn, n);
|
||
|
|
b.writeUInt32BE(n.poll_sequence, K2PROXY_NONCE_SIZE);
|
||
|
|
b.writeUInt32BE(n.send_sequence, K2PROXY_NONCE_SIZE + K2PROXY_SEQUENCE_NUMBER_SIZE);
|
||
|
|
encryptMessage(b);
|
||
|
|
response.writeHead(200, {"Content-Type": "text/plain"});
|
||
|
|
response.write(b.toString('base64'));
|
||
|
|
response.write('\r\n.\r\n');
|
||
|
|
response.end();
|
||
|
|
}, INIT_RESPONSE_DELAY_MS);
|
||
|
|
|
||
|
|
} else {
|
||
|
|
router.show404(response);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handler for HTTP GET '/recv'
|
||
|
|
*/
|
||
|
|
function recv(response, poll_message) {
|
||
|
|
var buf, sequence, urn, old_response, packet;
|
||
|
|
buf = new Buffer(poll_message, 'base64');
|
||
|
|
if (decryptMessage(buf)) {
|
||
|
|
sequence = buf.readUInt32BE(K2PROXY_NONCE_SIZE);
|
||
|
|
urn = buf.slice(K2PROXY_NONCE_SIZE + K2PROXY_SEQUENCE_NUMBER_SIZE, -K2PROXY_MIC_SIZE).toString('utf8');
|
||
|
|
trace('recv(' + urn + '), seq(' + sequence + ')');
|
||
|
|
|
||
|
|
// if an old response object is mapped, complete it with 'timeout' status
|
||
|
|
old_response = k2_map.get_response(urn);
|
||
|
|
if (typeof old_response === 'object') {
|
||
|
|
trace_info('completing deferred /recv/ response with timeout status');
|
||
|
|
poll_timeout(old_response);
|
||
|
|
}
|
||
|
|
|
||
|
|
// if a packet is waiting, return it now, else save the response object for when a packet arrives
|
||
|
|
if (k2_map.check_poll_sequence(urn, sequence)) {
|
||
|
|
packet = k2_map.get_packet(urn);
|
||
|
|
if (typeof packet === 'object') {
|
||
|
|
finish_recv(response, packet);
|
||
|
|
} else {
|
||
|
|
k2_map.set_response(urn, response);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
router.show404(response);
|
||
|
|
}
|
||
|
|
|
||
|
|
} else {
|
||
|
|
router.show404(response);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
Handler for HTTP POST '/send'
|
||
|
|
*/
|
||
|
|
function send(response, b64) {
|
||
|
|
var buf, sequence, packet, meta;
|
||
|
|
buf = new Buffer(b64, 'base64');
|
||
|
|
trace('send "' + b64 + '"');
|
||
|
|
if (!decryptMessage(buf)) {
|
||
|
|
trace('send: decrypt failed');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
sequence = buf.readUInt32BE(K2PROXY_NONCE_SIZE);
|
||
|
|
packet = buf.slice(K2PROXY_NONCE_SIZE + K2PROXY_SEQUENCE_NUMBER_SIZE, -K2PROXY_MIC_SIZE);
|
||
|
|
meta = {};
|
||
|
|
if (decodePacket(packet, meta)) {
|
||
|
|
showK2Packet('Tx', packet.length, meta, sequence);
|
||
|
|
if (k2_map.check_send_sequence(meta.urn, sequence)) {
|
||
|
|
encodePacket(packet, meta.inum);
|
||
|
|
k2_udp.send(packet);
|
||
|
|
response.writeHead(200, {"Content-Type": "text/plain"});
|
||
|
|
response.write(SEND_MESSAGE_RESPONSE);
|
||
|
|
response.end();
|
||
|
|
} else {
|
||
|
|
router.show404(response);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
router.show404(response);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
exports.MIN_BASE64_MESSAGE_LEN = MIN_BASE64_MESSAGE_LEN;
|
||
|
|
exports.init = init;
|
||
|
|
exports.recv = recv;
|
||
|
|
exports.send = send;
|