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; };
50 //require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: str });
51 if (obj.parent.debug) { console.log(str); }
53 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); }
54 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; }
55 function binzerostring(len) { var res = ''; for (var l = 0; l < len; l++) { res += String.fromCharCode(0 & 0xFF); } return res; }
60 PROTOCOL_VERSION_SENT: 1,
61 AUTH_SERVICE_REQUEST_SENT: 2,
63 PFWD_SERVICE_REQUEST_SENT: 4,
64 GLOBAL_REQUEST_SENT: 5,
67 obj.cirastate = CIRASTATE.INITIAL;
77 // redirection start command
78 obj.RedirectStartSol = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x53, 0x4F, 0x4C, 0x20);
79 obj.RedirectStartKvm = String.fromCharCode(0x10, 0x01, 0x00, 0x00, 0x4b, 0x56, 0x4d, 0x52);
80 obj.RedirectStartIder = String.fromCharCode(0x10, 0x00, 0x00, 0x00, 0x49, 0x44, 0x45, 0x52);
82 // Intel AMT forwarded port list for non-TLS mode
83 //var pfwd_ports = [16992, 623, 16994, 5900];
84 var pfwd_ports = [ 16992, 16993 ];
86 // protocol definitions
99 CHANNEL_OPEN_CONFIRMATION: 91,
100 CHANNEL_OPEN_FAILURE: 92,
101 CHANNEL_WINDOW_ADJUST: 93,
104 PROTOCOLVERSION: 192,
105 KEEPALIVE_REQUEST: 208,
106 KEEPALIVE_REPLY: 209,
107 KEEPALIVE_OPTIONS_REQUEST: 210,
108 KEEPALIVE_OPTIONS_REPLY: 211,
109 JSON_CONTROL: 250 // This is a Mesh specific command that sends JSON to and from the MPS server.
112 var APFDisconnectCode = {
113 HOST_NOT_ALLOWED_TO_CONNECT: 1,
115 KEY_EXCHANGE_FAILED: 3,
118 COMPRESSION_ERROR: 6,
119 SERVICE_NOT_AVAILABLE: 7,
120 PROTOCOL_VERSION_NOT_SUPPORTED: 8,
121 HOST_KEY_NOT_VERIFIABLE: 9,
124 TOO_MANY_CONNECTIONS: 12,
125 AUTH_CANCELLED_BY_USER: 13,
126 NO_MORE_AUTH_METHODS_AVAILABLE: 14,
127 INVALID_CREDENTIALS: 15,
128 CONNECTION_TIMED_OUT: 16,
130 TEMPORARILY_UNAVAILABLE: 18
133 var APFChannelOpenFailCodes = {
134 ADMINISTRATIVELY_PROHIBITED: 1,
136 UNKNOWN_CHANNEL_TYPE: 3,
137 RESOURCE_SHORTAGE: 4,
140 var APFChannelOpenFailureReasonCode = {
141 AdministrativelyProhibited: 1,
143 UnknownChannelType: 3,
147 obj.onSecureConnect = function onSecureConnect(resp, ws, head) {
148 Debug("APF Secure WebSocket connected.");
149 //console.log(JSON.stringify(resp));
150 obj.forwardClient.tag = { accumulator: [] };
151 obj.forwardClient.ws = ws;
152 obj.forwardClient.ws.on('end', function () {
153 Debug("APF: Connection is closing.");
154 if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; }
155 if (obj.onChannelClosed) { obj.onChannelClosed(obj); }
158 obj.forwardClient.ws.on('data', function (data) {
159 obj.forwardClient.tag.accumulator += hex2rstr(buf2hex(data));
163 len = ProcessData(obj.forwardClient);
164 if (len > 0) { obj.forwardClient.tag.accumulator = obj.forwardClient.tag.accumulator.slice(len); }
165 if (obj.cirastate == CIRASTATE.FAILED) {
166 Debug("APF: in a failed state, destroying socket.");
167 obj.forwardClient.ws.end();
170 } catch (ex) { Debug(ex); }
173 obj.forwardClient.ws.on('error', function (e) {
174 Debug("APF: Connection error, ending connecting.");
175 if (obj.timer != null) { clearInterval(obj.timer); obj.timer = null; }
178 obj.state = CIRASTATE.INITIAL;
179 if ((typeof obj.args.conntype == 'number') && (obj.args.conntype != 0)) {
180 SendJsonControl(obj.forwardClient.ws, { action: 'connType', value: obj.args.conntype });
181 if (obj.args.meiState != null) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: obj.args.meiState }); }
183 SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid);
184 SendServiceRequest(obj.forwardClient.ws, 'auth@amt.intel.com');
187 obj.updateMeiState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'meiState', value: state }); }
188 obj.sendMeiDeactivationState = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'deactivate', value: state }); }
189 obj.sendStartTlsHostConfigResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'startTlsHostConfig', value: state }); }
190 obj.sendStopConfigurationResponse = function (state) { SendJsonControl(obj.forwardClient.ws, { action: 'stopConfiguration', value: state }); }
192 function SendJsonControl(socket, o) {
193 var data = JSON.stringify(o)
194 socket.write(String.fromCharCode(APFProtocol.JSON_CONTROL) + IntToStr(data.length) + data);
195 Debug("APF: Send JSON control: " + data);
198 function SendProtocolVersion(socket, uuid) {
199 var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64);
201 Debug("APF: Send protocol version 1 0 " + uuid);
202 obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT;
205 function SendServiceRequest(socket, service) {
206 var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service;
208 Debug("APF: Send service request " + service);
209 if (service == 'auth@amt.intel.com') {
210 obj.cirastate = CIRASTATE.AUTH_SERVICE_REQUEST_SENT;
211 } else if (service == 'pfwd@amt.intel.com') {
212 obj.cirastate = CIRASTATE.PFWD_SERVICE_REQUEST_SENT;
216 function SendUserAuthRequest(socket, user, pass) {
217 var service = "pfwd@amt.intel.com";
218 var data = String.fromCharCode(APFProtocol.USERAUTH_REQUEST) + IntToStr(user.length) + user + IntToStr(service.length) + service;
220 data += IntToStr(8) + 'password';
221 data += binzerostring(1) + IntToStr(pass.length) + pass;
223 Debug("APF: Send username password authentication to MPS");
224 obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT;
227 function SendGlobalRequestPfwd(socket, amthostname, amtport) {
228 var tcpipfwd = 'tcpip-forward';
229 var data = String.fromCharCode(APFProtocol.GLOBAL_REQUEST) + IntToStr(tcpipfwd.length) + tcpipfwd + binzerostring(1, 1);
230 data += IntToStr(amthostname.length) + amthostname + IntToStr(amtport);
232 Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport);
233 obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT;
236 function SendKeepAliveRequest(socket) {
237 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255));
238 Debug("APF: Send keepalive request");
241 function SendKeepAliveReply(socket, cookie) {
242 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie));
243 Debug("APF: Send keepalive reply");
246 function ProcessData(socket) {
247 var cmd = socket.tag.accumulator.charCodeAt(0);
248 var len = socket.tag.accumulator.length;
249 var data = socket.tag.accumulator;
250 if (len == 0) { return 0; }
252 // Respond to MPS according to obj.cirastate
254 case APFProtocol.SERVICE_ACCEPT: {
255 var slen = ReadInt(data, 1), service = data.substring(5, 6 + slen);
256 Debug("APF: Service request to " + service + " accepted.");
257 if (service == 'auth@amt.intel.com') {
258 if (obj.cirastate >= CIRASTATE.AUTH_SERVICE_REQUEST_SENT) {
259 SendUserAuthRequest(socket.ws, obj.args.mpsuser, obj.args.mpspass);
261 } else if (service == 'pfwd@amt.intel.com') {
262 if (obj.cirastate >= CIRASTATE.PFWD_SERVICE_REQUEST_SENT) {
263 SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]);
268 case APFProtocol.REQUEST_SUCCESS: {
270 var port = ReadInt(data, 1);
271 Debug("APF: Request to port forward " + port + " successful.");
272 // iterate to pending port forward request
273 if (obj.pfwd_idx < pfwd_ports.length) {
274 SendGlobalRequestPfwd(socket.ws, obj.args.clientname, pfwd_ports[obj.pfwd_idx++]);
276 // no more port forward, now setup timer to send keep alive
277 Debug("APF: Start keep alive for every " + obj.args.mpskeepalive + " ms.");
278 obj.timer = setInterval(function () {
279 SendKeepAliveRequest(obj.forwardClient.ws);
280 }, obj.args.mpskeepalive);//
284 Debug("APF: Request successful.");
287 case APFProtocol.USERAUTH_SUCCESS: {
288 Debug("APF: User Authentication successful");
289 // Send Pfwd service request
290 SendServiceRequest(socket.ws, 'pfwd@amt.intel.com');
293 case APFProtocol.USERAUTH_FAILURE: {
294 Debug("APF: User Authentication failed");
295 obj.cirastate = CIRASTATE.FAILED;
298 case APFProtocol.KEEPALIVE_REQUEST: {
299 Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1));
300 SendKeepAliveReply(socket.ws, ReadInt(data, 1));
303 case APFProtocol.KEEPALIVE_REPLY: {
304 Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1));
307 // Channel management
308 case APFProtocol.CHANNEL_OPEN: {
309 // Parse CHANNEL OPEN request
310 var p_res = parseChannelOpen(data);
311 Debug("APF: CHANNEL_OPEN request: " + JSON.stringify(p_res));
312 // Check if target port is in pfwd_ports
313 if (pfwd_ports.indexOf(p_res.target_port) >= 0) {
314 // Connect socket to that port
315 var chan = obj.net.createConnection({ host: obj.args.clientaddress, port: p_res.target_port }, function () {
316 //require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: "CHANNEL_OPEN-open" });
317 // obj.downlinks[p_res.sender_chan].setEncoding('binary');//assume everything is binary, not interpreting
318 SendChannelOpenConfirm(socket.ws, p_res);
321 // Setup flow control
322 chan.maxInWindow = p_res.window_size; // Oddly, we are using the same window size as the other side.
323 chan.curInWindow = 0;
325 chan.on('data', function (ddata) {
326 // Relay data to fordwardclient
327 // TODO: Implement flow control
328 SendChannelData(socket.ws, p_res.sender_chan, ddata);
331 chan.on('error', function (e) {
332 //Debug("Downlink connection error: " + e);
333 SendChannelOpenFailure(socket.ws, p_res);
336 chan.on('end', function () {
337 var chan = obj.downlinks[p_res.sender_chan];
339 Debug("Socket ends.");
340 try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { }
341 delete obj.downlinks[p_res.sender_chan];
345 obj.downlinks[p_res.sender_chan] = chan;
347 // Not a supported port, fail the connection
348 SendChannelOpenFailure(socket.ws, p_res);
352 case APFProtocol.CHANNEL_OPEN_CONFIRMATION: {
353 Debug("APF: CHANNEL_OPEN_CONFIRMATION");
356 case APFProtocol.CHANNEL_CLOSE: {
357 var rcpt_chan = ReadInt(data, 1);
358 Debug("APF: CHANNEL_CLOSE: " + rcpt_chan);
359 try { obj.downlinks[rcpt_chan].end(); } catch (ex) { }
362 case APFProtocol.CHANNEL_DATA: {
363 Debug("APF: CHANNEL_DATA: " + JSON.stringify(rstr2hex(data)));
364 var rcpt_chan = ReadInt(data, 1);
365 var chan_data_len = ReadInt(data, 5);
366 var chan_data = data.substring(9, 9 + chan_data_len);
367 var chan = obj.downlinks[rcpt_chan];
369 chan.curInWindow += chan_data_len;
371 chan.write(Buffer.from(chan_data, 'binary'), function () {
372 Debug("Write completed.");
373 // If the incoming window is over half used, send an adjust.
374 if (this.curInWindow > (this.maxInWindow / 2)) { SendChannelWindowAdjust(socket.ws, rcpt_chan, this.curInWindow); this.curInWindow = 0; }
376 } catch (ex) { Debug("Cannot forward data to downlink socket."); }
378 return 9 + chan_data_len;
380 case APFProtocol.CHANNEL_WINDOW_ADJUST: {
381 Debug("APF: CHANNEL_WINDOW_ADJUST");
384 case APFProtocol.JSON_CONTROL: {
385 Debug("APF: JSON_CONTROL");
386 var len = ReadInt(data, 1);
387 if (obj.onJsonControl) { var o = null; try { o = JSON.parse(data.substring(5, 5 + len)); } catch (ex) { } if (o != null) { obj.onJsonControl(o); } }
391 Debug("CMD: " + cmd + " is not implemented.");
392 obj.cirastate = CIRASTATE.FAILED;
398 function parseChannelOpen(data) {
399 var result = { cmd: APFProtocol.CHANNEL_OPEN };
400 var chan_type_slen = ReadInt(data, 1);
401 result.chan_type = data.substring(5, 5 + chan_type_slen);
402 result.sender_chan = ReadInt(data, 5 + chan_type_slen);
403 result.window_size = ReadInt(data, 9 + chan_type_slen);
404 var c_len = ReadInt(data, 17 + chan_type_slen);
405 result.target_address = data.substring(21 + chan_type_slen, 21 + chan_type_slen + c_len);
406 result.target_port = ReadInt(data, 21 + chan_type_slen + c_len);
407 var o_len = ReadInt(data, 25 + chan_type_slen + c_len);
408 result.origin_address = data.substring(29 + chan_type_slen + c_len, 29 + chan_type_slen + c_len + o_len);
409 result.origin_port = ReadInt(data, 29 + chan_type_slen + c_len + o_len);
410 result.len = 33 + chan_type_slen + c_len + o_len;
414 function SendChannelOpenFailure(socket, chan_data) {
415 socket.write(String.fromCharCode(APFProtocol.CHANNEL_OPEN_FAILURE) + IntToStr(chan_data.sender_chan) + IntToStr(2) + IntToStr(0) + IntToStr(0));
416 Debug("APF: Send ChannelOpenFailure");
419 function SendChannelOpenConfirm(socket, chan_data) {
420 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));
421 Debug("APF: Send ChannelOpenConfirmation");
424 function SendChannelWindowAdjust(socket, chan, size) {
425 socket.write(String.fromCharCode(APFProtocol.CHANNEL_WINDOW_ADJUST) + IntToStr(chan) + IntToStr(size));
426 Debug("APF: Send ChannelWindowAdjust, channel: " + chan + ", size: " + size);
429 function SendChannelData(socket, chan, data) {
430 socket.write(Buffer.concat([Buffer.from(String.fromCharCode(APFProtocol.CHANNEL_DATA) + IntToStr(chan) + IntToStr(data.length), 'binary'), data]));
431 Debug("APF: Send ChannelData: " + data.toString('hex'));
434 function SendChannelClose(socket, chan) {
435 socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan));
436 Debug("APF: Send ChannelClose ");
439 obj.connect = function () {
440 if (obj.forwardClient != null) {
441 try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); }
442 //obj.forwardClient = null;
444 obj.cirastate = CIRASTATE.INITIAL;
447 //obj.forwardClient = new obj.ws(obj.args.mpsurl, obj.tlsoptions);
448 //obj.forwardClient.on("open", obj.onSecureConnect);
450 var wsoptions = obj.http.parseUri(obj.args.mpsurl);
451 wsoptions.rejectUnauthorized = 0;
452 obj.forwardClient = obj.http.request(wsoptions);
453 obj.forwardClient.upgrade = obj.onSecureConnect;
454 obj.forwardClient.end(); // end request, trigger completion of HTTP request
457 obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } }
462module.exports = CreateAPFClient;