2Copyright 2018-2021 Intel Corporation
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
18* @description APF/CIRA Client for Duktape
19* @author Joko Sastriawan & Ylian Saint-Hilaire
20* @copyright Intel Corporation 2020-2021
25function CreateAPFClient(parent, args) {
26 if ((args.clientuuid == null) || (args.clientuuid.length != 36)) return null; // Require a UUID if this exact length
31 obj.http = require('http');
32 obj.net = require('net');
33 obj.forwardClient = null;
36 obj.timer = null; // Keep alive timer
38 // obj.onChannelClosed
41 // Function copied from common.js
42 function ReadInt(v, p) { return (v.charCodeAt(p) * 0x1000000) + (v.charCodeAt(p + 1) << 16) + (v.charCodeAt(p + 2) << 8) + v.charCodeAt(p + 3); }; // We use "*0x1000000" instead of "<<24" because the shift converts the number to signed int32.
43 function IntToStr(v) { return String.fromCharCode((v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF); };
44 function hex2rstr(d) { var r = '', m = ('' + d).match(/../g), t; while (t = m.shift()) { r += String.fromCharCode('0x' + t); } return r; };
45 function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }; // Convert decimal to hex
46 function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }; // Convert a raw string to a hex string
47 function d2h(d) { return (d / 256 + 1 / 512).toString(16).substring(2, 4); }
48 function buf2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += d2h(input[i]); } return r; };
49 function Debug(str) { if (obj.parent.debug) { console.log(str); } }
50 function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); }
51 function strToGuid(s) { s = s.replace(/-/g, ''); var ret = s.substring(6, 8) + s.substring(4, 6) + s.substring(2, 4) + s.substring(0, 2) + s.substring(10, 12) + s.substring(8, 10) + s.substring(14, 16) + s.substring(12, 14) + s.substring(16, 20) + s.substring(20); return ret; }
52 function binzerostring(len) { var res = ''; for (var l = 0; l < len; l++) { res += String.fromCharCode(0 & 0xFF); } return res; }
57 PROTOCOL_VERSION_SENT: 1,
58 AUTH_SERVICE_REQUEST_SENT: 2,
60 PFWD_SERVICE_REQUEST_SENT: 4,
61 GLOBAL_REQUEST_SENT: 5,
64 obj.cirastate = CIRASTATE.INITIAL;
74 // redirection start command
75 obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
76 obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
77 obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
79 // Intel AMT forwarded port list for non-TLS mode
80 //var pfwd_ports = [16992, 623, 16994, 5900];
81 var pfwd_ports = [ 16992, 16993 ];
83 // protocol definitions
96 CHANNEL_OPEN_CONFIRMATION: 91,
97 CHANNEL_OPEN_FAILURE: 92,
98 CHANNEL_WINDOW_ADJUST: 93,
101 PROTOCOLVERSION: 192,
102 KEEPALIVE_REQUEST: 208,
103 KEEPALIVE_REPLY: 209,
104 KEEPALIVE_OPTIONS_REQUEST: 210,
105 KEEPALIVE_OPTIONS_REPLY: 211,
106 JSON_CONTROL: 250 // This is a Mesh specific command that sends JSON to and from the MPS server.
109 var APFDisconnectCode = {
110 HOST_NOT_ALLOWED_TO_CONNECT: 1,
112 KEY_EXCHANGE_FAILED: 3,
115 COMPRESSION_ERROR: 6,
116 SERVICE_NOT_AVAILABLE: 7,
117 PROTOCOL_VERSION_NOT_SUPPORTED: 8,
118 HOST_KEY_NOT_VERIFIABLE: 9,
121 TOO_MANY_CONNECTIONS: 12,
122 AUTH_CANCELLED_BY_USER: 13,
123 NO_MORE_AUTH_METHODS_AVAILABLE: 14,
124 INVALID_CREDENTIALS: 15,
125 CONNECTION_TIMED_OUT: 16,
127 TEMPORARILY_UNAVAILABLE: 18
130 var APFChannelOpenFailCodes = {
131 ADMINISTRATIVELY_PROHIBITED: 1,
133 UNKNOWN_CHANNEL_TYPE: 3,
134 RESOURCE_SHORTAGE: 4,
137 var APFChannelOpenFailureReasonCode = {
138 AdministrativelyProhibited: 1,
140 UnknownChannelType: 3,
144 obj.onSecureConnect = function onSecureConnect(resp, ws, head) {
145 Debug("APF Secure WebSocket connected.");
146 //console.log(JSON.stringify(resp));
147 obj.forwardClient.tag = { accumulator: [] };
148 obj.forwardClient.ws = ws;
149 obj.forwardClient.ws.on('end', function () {
150 Debug("APF: Connection is closing.");
151 if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; }
152 if (obj.onChannelClosed) { obj.onChannelClosed(obj); }
155 obj.forwardClient.ws.on('data', function (data) {
156 obj.forwardClient.tag.accumulator += hex2rstr(buf2hex(data));
160 len = ProcessData(obj.forwardClient);
161 if (len > 0) { obj.forwardClient.tag.accumulator = obj.forwardClient.tag.accumulator.slice(len); }
162 if (obj.cirastate == CIRASTATE.FAILED) {
163 Debug("APF: in a failed state, destroying socket.");
164 obj.forwardClient.ws.end();
167 } catch (ex) { Debug(ex); }
170 obj.forwardClient.ws.on('error', function (e) {
171 Debug("APF: Connection error, ending connecting.");
172 if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; }
175 obj.state = CIRASTATE.INITIAL;
176 if ((typeof obj.args.conntype == 'number') && (obj.args.conntype != 0)) {
177 SendJsonControl(obj.forwardClient.ws, { action: 'connType', value: obj.args.conntype });
178 if (obj.args.meiState != null) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: obj.args.meiState }); }
180 SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid);
181 SendServiceRequest(obj.forwardClient.ws, 'auth@amt.intel.com');
184 obj.updateMeiState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: state }); }
185 obj.sendMeiDeactivationState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'deactivate', value: state }); }
186 obj.sendStartTlsHostConfigResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'startTlsHostConfig', value: state }); }
187 obj.sendStopConfigurationResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'stopConfiguration', value: state }); }
189 function SendJsonControl(socket, o) {
190 var data = JSON.stringify(o)
191 socket.write(String.fromCharCode(APFProtocol.JSON_CONTROL) + IntToStr(data.length) + data);
192 Debug("APF: Send JSON control: " + data);
195 function SendProtocolVersion(socket, uuid) {
196 var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64);
198 Debug("APF: Send protocol version 1 0 " + uuid);
199 obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT;
202 function SendServiceRequest(socket, service) {
203 var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service;
205 Debug("APF: Send service request " + service);
206 if (service == 'auth@amt.intel.com') {
207 obj.cirastate = CIRASTATE.AUTH_SERVICE_REQUEST_SENT;
208 } else if (service == 'pfwd@amt.intel.com') {
209 obj.cirastate = CIRASTATE.PFWD_SERVICE_REQUEST_SENT;
213 function SendUserAuthRequest(socket, user, pass) {
214 var service = "pfwd@amt.intel.com";
215 var data = String.fromCharCode(APFProtocol.USERAUTH_REQUEST) + IntToStr(user.length) + user + IntToStr(service.length) + service;
217 data += IntToStr(8) + 'password';
218 data += binzerostring(1) + IntToStr(pass.length) + pass;
220 Debug("APF: Send username password authentication to MPS");
221 obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT;
224 function SendGlobalRequestPfwd(socket, amthostname, amtport) {
225 var tcpipfwd = 'tcpip-forward';
226 var data = String.fromCharCode(APFProtocol.GLOBAL_REQUEST) + IntToStr(tcpipfwd.length) + tcpipfwd + binzerostring(1, 1);
227 data += IntToStr(amthostname.length) + amthostname + IntToStr(amtport);
229 Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport);
230 obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT;
233 function SendKeepAliveRequest(socket) {
234 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255));
235 Debug("APF: Send keepalive request");
238 function SendKeepAliveReply(socket, cookie) {
239 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie));
240 Debug("APF: Send keepalive reply");
243 function ProcessData(socket) {
244 var cmd = socket.tag.accumulator.charCodeAt(0);
245 var len = socket.tag.accumulator.length;
246 var data = socket.tag.accumulator;
247 if (len == 0) { return 0; }
249 // Respond to MPS according to obj.cirastate
251 case APFProtocol.SERVICE_ACCEPT: {
252 var slen = ReadInt(data, 1), service = data.substring(5, 6 + slen);
253 Debug("APF: Service request to " + service + " accepted.");
254 if (service == 'auth@amt.intel.com') {
255 if (obj.cirastate >= CIRASTATE.AUTH_SERVICE_REQUEST_SENT) {
256 SendUserAuthRequest(socket.ws, obj.args.mpsuser, obj.args.mpspass);
258 } else if (service == 'pfwd@amt.intel.com') {
259 if (obj.cirastate >= CIRASTATE.PFWD_SERVICE_REQUEST_SENT) {
260 SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]);
265 case APFProtocol.REQUEST_SUCCESS: {
267 var port = ReadInt(data, 1);
268 Debug("APF: Request to port forward " + port + " successful.");
269 // iterate to pending port forward request
270 if (obj.pfwd_idx < pfwd_ports.length) {
271 SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]);
273 // no more port forward, now setup timer to send keep alive
274 Debug("APF: Start keep alive for every " + obj.args.mpskeepalive + " ms.");
275 obj.timer = setInterval(function () {
276 SendKeepAliveRequest(obj.forwardClient.ws);
277 }, obj.args.mpskeepalive);//
281 Debug("APF: Request successful.");
284 case APFProtocol.USERAUTH_SUCCESS: {
285 Debug("APF: User Authentication successful");
286 // Send Pfwd service request
287 SendServiceRequest(socket.ws, 'pfwd@amt.intel.com');
290 case APFProtocol.USERAUTH_FAILURE: {
291 Debug("APF: User Authentication failed");
292 obj.cirastate = CIRASTATE.FAILED;
295 case APFProtocol.KEEPALIVE_REQUEST: {
296 Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1));
297 SendKeepAliveReply(socket.ws, ReadInt(data, 1));
300 case APFProtocol.KEEPALIVE_REPLY: {
301 Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1));
304 // Channel management
305 case APFProtocol.CHANNEL_OPEN: {
306 // Parse CHANNEL OPEN request
307 var p_res = parseChannelOpen(data);
308 Debug("APF: CHANNEL_OPEN request: " + JSON.stringify(p_res));
309 // Check if target port is in pfwd_ports
310 if (pfwd_ports.indexOf(p_res.target_port) >= 0) {
311 // Connect socket to that port
312 var chan = obj.net.createConnection({ host: obj.args.clientaddress, port: p_res.target_port }, function () {
313 //require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "CHANNEL_OPEN-open" });
314 // obj.downlinks[p_res.sender_chan].setEncoding('binary');//assume everything is binary, not interpreting
315 SendChannelOpenConfirm(socket.ws, p_res);
318 // Setup flow control
319 chan.maxInWindow = p_res.window_size; // Oddly, we are using the same window size as the other side.
320 chan.curInWindow = 0;
322 chan.on('data', function (ddata) {
323 // Relay data to fordwardclient
324 // TODO: Implement flow control
325 SendChannelData(socket.ws, p_res.sender_chan, ddata);
328 chan.on('error', function (e) {
329 //Debug("Downlink connection error: " + e);
330 SendChannelOpenFailure(socket.ws, p_res);
333 chan.on('end', function () {
334 var chan = obj.downlinks[p_res.sender_chan];
336 Debug("Socket ends.");
337 try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { }
338 delete obj.downlinks[p_res.sender_chan];
342 obj.downlinks[p_res.sender_chan] = chan;
344 // Not a supported port, fail the connection
345 SendChannelOpenFailure(socket.ws, p_res);
349 case APFProtocol.CHANNEL_OPEN_CONFIRMATION: {
350 Debug("APF: CHANNEL_OPEN_CONFIRMATION");
353 case APFProtocol.CHANNEL_CLOSE: {
354 var rcpt_chan = ReadInt(data, 1);
355 Debug("APF: CHANNEL_CLOSE: " + rcpt_chan);
356 try { obj.downlinks[rcpt_chan].end(); } catch (ex) { }
359 case APFProtocol.CHANNEL_DATA: {
360 Debug("APF: CHANNEL_DATA: " + JSON.stringify(rstr2hex(data)));
361 var rcpt_chan = ReadInt(data, 1);
362 var chan_data_len = ReadInt(data, 5);
363 var chan_data = data.substring(9, 9 + chan_data_len);
364 var chan = obj.downlinks[rcpt_chan];
366 chan.curInWindow += chan_data_len;
368 chan.write(Buffer.from(chan_data, 'binary'), function () {
369 Debug("Write completed.");
370 // If the incoming window is over half used, send an adjust.
371 if (this.curInWindow > (this.maxInWindow / 2)) { SendChannelWindowAdjust(socket.ws, rcpt_chan, this.curInWindow); this.curInWindow = 0; }
373 } catch (ex) { Debug("Cannot forward data to downlink socket."); }
375 return 9 + chan_data_len;
377 case APFProtocol.CHANNEL_WINDOW_ADJUST: {
378 Debug("APF: CHANNEL_WINDOW_ADJUST");
381 case APFProtocol.JSON_CONTROL: {
382 Debug("APF: JSON_CONTROL");
383 var len = ReadInt(data, 1);
384 if (obj.onJsonControl) { var o = null; try { o = JSON.parse(data.substring(5, 5 + len)); } catch (ex) { } if (o != null) { obj.onJsonControl(o); } }
388 Debug("CMD: " + cmd + " is not implemented.");
389 obj.cirastate = CIRASTATE.FAILED;
395 function parseChannelOpen(data) {
396 var result = { cmd: APFProtocol.CHANNEL_OPEN };
397 var chan_type_slen = ReadInt(data, 1);
398 result.chan_type = data.substring(5, 5 + chan_type_slen);
399 result.sender_chan = ReadInt(data, 5 + chan_type_slen);
400 result.window_size = ReadInt(data, 9 + chan_type_slen);
401 var c_len = ReadInt(data, 17 + chan_type_slen);
402 result.target_address = data.substring(21 + chan_type_slen, 21 + chan_type_slen + c_len);
403 result.target_port = ReadInt(data, 21 + chan_type_slen + c_len);
404 var o_len = ReadInt(data, 25 + chan_type_slen + c_len);
405 result.origin_address = data.substring(29 + chan_type_slen + c_len, 29 + chan_type_slen + c_len + o_len);
406 result.origin_port = ReadInt(data, 29 + chan_type_slen + c_len + o_len);
407 result.len = 33 + chan_type_slen + c_len + o_len;
411 function SendChannelOpenFailure(socket, chan_data) {
412 socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + IntToStr(chan_data.sender_chan) + IntToStr(2) + IntToStr(0) + IntToStr(0));
413 Debug("APF: Send ChannelOpenFailure");
416 function SendChannelOpenConfirm(socket, chan_data) {
417 socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_CONFIRMATION) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.sender_chan) + IntToStr(chan_data.window_size) + IntToStr(0xFFFFFFFF));
418 Debug("APF: Send ChannelOpenConfirmation");
421 function SendChannelWindowAdjust(socket, chan, size) {
422 socket.write(String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + IntToStr(chan) + IntToStr(size));
423 Debug("APF: Send ChannelWindowAdjust, channel: " + chan + ", size: " + size);
426 function SendChannelData(socket, chan, data) {
427 socket.write(Buffer.concat([Buffer.from(String.fromCharCode(APFProtocol.CHANNEL_DATA) + IntToStr(chan) + IntToStr(data.length), 'binary'), data]));
428 Debug("APF: Send ChannelData: " + data.toString('hex'));
431 function SendChannelClose(socket, chan) {
432 socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan));
433 Debug("APF: Send ChannelClose ");
436 obj.connect = function () {
437 if (obj.forwardClient != null) {
438 try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); }
439 //obj.forwardClient = null;
441 obj.cirastate = CIRASTATE.INITIAL;
444 //obj.forwardClient = new obj.ws(obj.args.mpsurl, obj.tlsoptions);
445 //obj.forwardClient.on("open", obj.onSecureConnect);
447 var wsoptions = obj.http.parseUri(obj.args.mpsurl);
448 wsoptions.rejectUnauthorized = 0;
449 obj.forwardClient = obj.http.request(wsoptions);
450 obj.forwardClient.upgrade = obj.onSecureConnect;
451 obj.forwardClient.end(); // end request, trigger completion of HTTP request
454 obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } }
459module.exports = CreateAPFClient;