EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
amt-apfclient.js
Go to the documentation of this file.
1/*
2Copyright 2018-2021 Intel Corporation
3
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
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
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.
15*/
16
17/**
18* @description APF/CIRA Client for Duktape
19* @author Joko Sastriawan & Ylian Saint-Hilaire
20* @copyright Intel Corporation 2020-2021
21* @license Apache-2.0
22* @version v0.0.2
23*/
24
25function CreateAPFClient(parent, args) {
26 if ((args.clientuuid == null) || (args.clientuuid.length != 36)) return null; // Require a UUID if this exact length
27
28 var obj = {};
29 obj.parent = parent;
30 obj.args = args;
31 obj.http = require('http');
32 obj.net = require('net');
33 obj.forwardClient = null;
34 obj.downlinks = {};
35 obj.pfwd_idx = 0;
36 obj.timer = null; // Keep alive timer
37
38 // obj.onChannelClosed
39 // obj.onJsonControl
40
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) {
50 //require('MeshAgent').SendCommand({ action: 'msg', type: 'console', value: str });
51 if (obj.parent.debug) { console.log(str); }
52 }
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; }
56
57 // CIRA state
58 var CIRASTATE = {
59 INITIAL: 0,
60 PROTOCOL_VERSION_SENT: 1,
61 AUTH_SERVICE_REQUEST_SENT: 2,
62 AUTH_REQUEST_SENT: 3,
63 PFWD_SERVICE_REQUEST_SENT: 4,
64 GLOBAL_REQUEST_SENT: 5,
65 FAILED: -1
66 }
67 obj.cirastate = CIRASTATE.INITIAL;
68
69 // REDIR state
70 var REDIR_TYPE = {
71 REDIR_UNKNOWN: 0,
72 REDIR_SOL: 1,
73 REDIR_KVM: 2,
74 REDIR_IDER: 3
75 }
76
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);
81
82 // Intel AMT forwarded port list for non-TLS mode
83 //var pfwd_ports = [16992, 623, 16994, 5900];
84 var pfwd_ports = [ 16992, 16993 ];
85
86 // protocol definitions
87 var APFProtocol = {
88 UNKNOWN: 0,
89 DISCONNECT: 1,
90 SERVICE_REQUEST: 5,
91 SERVICE_ACCEPT: 6,
92 USERAUTH_REQUEST: 50,
93 USERAUTH_FAILURE: 51,
94 USERAUTH_SUCCESS: 52,
95 GLOBAL_REQUEST: 80,
96 REQUEST_SUCCESS: 81,
97 REQUEST_FAILURE: 82,
98 CHANNEL_OPEN: 90,
99 CHANNEL_OPEN_CONFIRMATION: 91,
100 CHANNEL_OPEN_FAILURE: 92,
101 CHANNEL_WINDOW_ADJUST: 93,
102 CHANNEL_DATA: 94,
103 CHANNEL_CLOSE: 97,
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.
110 }
111
112 var APFDisconnectCode = {
113 HOST_NOT_ALLOWED_TO_CONNECT: 1,
114 PROTOCOL_ERROR: 2,
115 KEY_EXCHANGE_FAILED: 3,
116 RESERVED: 4,
117 MAC_ERROR: 5,
118 COMPRESSION_ERROR: 6,
119 SERVICE_NOT_AVAILABLE: 7,
120 PROTOCOL_VERSION_NOT_SUPPORTED: 8,
121 HOST_KEY_NOT_VERIFIABLE: 9,
122 CONNECTION_LOST: 10,
123 BY_APPLICATION: 11,
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,
129 BY_POLICY: 17,
130 TEMPORARILY_UNAVAILABLE: 18
131 }
132
133 var APFChannelOpenFailCodes = {
134 ADMINISTRATIVELY_PROHIBITED: 1,
135 CONNECT_FAILED: 2,
136 UNKNOWN_CHANNEL_TYPE: 3,
137 RESOURCE_SHORTAGE: 4,
138 }
139
140 var APFChannelOpenFailureReasonCode = {
141 AdministrativelyProhibited: 1,
142 ConnectFailed: 2,
143 UnknownChannelType: 3,
144 ResourceShortage: 4,
145 }
146
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); }
156 });
157
158 obj.forwardClient.ws.on('data', function (data) {
159 obj.forwardClient.tag.accumulator += hex2rstr(buf2hex(data));
160 try {
161 var len = 0;
162 do {
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();
168 }
169 } while (len > 0);
170 } catch (ex) { Debug(ex); }
171 });
172
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; }
176 });
177
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 }); }
182 }
183 SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid);
184 SendServiceRequest(obj.forwardClient.ws, 'auth@amt.intel.com');
185 }
186
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 }); }
191
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);
196 }
197
198 function SendProtocolVersion(socket, uuid) {
199 var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64);
200 socket.write(data);
201 Debug("APF: Send protocol version 1 0 " + uuid);
202 obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT;
203 }
204
205 function SendServiceRequest(socket, service) {
206 var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service;
207 socket.write(data);
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;
213 }
214 }
215
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;
219 //password auth
220 data += IntToStr(8) + 'password';
221 data += binzerostring(1) + IntToStr(pass.length) + pass;
222 socket.write(data);
223 Debug("APF: Send username password authentication to MPS");
224 obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT;
225 }
226
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);
231 socket.write(data);
232 Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport);
233 obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT;
234 }
235
236 function SendKeepAliveRequest(socket) {
237 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255));
238 Debug("APF: Send keepalive request");
239 }
240
241 function SendKeepAliveReply(socket, cookie) {
242 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie));
243 Debug("APF: Send keepalive reply");
244 }
245
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; }
251
252 // Respond to MPS according to obj.cirastate
253 switch (cmd) {
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);
260 }
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++]);
264 }
265 }
266 return 5 + slen;
267 }
268 case APFProtocol.REQUEST_SUCCESS: {
269 if (len >= 5) {
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++]);
275 } else {
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);//
281 }
282 return 5;
283 }
284 Debug("APF: Request successful.");
285 return 1;
286 }
287 case APFProtocol.USERAUTH_SUCCESS: {
288 Debug("APF: User Authentication successful");
289 // Send Pfwd service request
290 SendServiceRequest(socket.ws, 'pfwd@amt.intel.com');
291 return 1;
292 }
293 case APFProtocol.USERAUTH_FAILURE: {
294 Debug("APF: User Authentication failed");
295 obj.cirastate = CIRASTATE.FAILED;
296 return 14;
297 }
298 case APFProtocol.KEEPALIVE_REQUEST: {
299 Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1));
300 SendKeepAliveReply(socket.ws, ReadInt(data, 1));
301 return 5;
302 }
303 case APFProtocol.KEEPALIVE_REPLY: {
304 Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1));
305 return 5;
306 }
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);
319 });
320
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;
324
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);
329 });
330
331 chan.on('error', function (e) {
332 //Debug("Downlink connection error: " + e);
333 SendChannelOpenFailure(socket.ws, p_res);
334 });
335
336 chan.on('end', function () {
337 var chan = obj.downlinks[p_res.sender_chan];
338 if (chan != null) {
339 Debug("Socket ends.");
340 try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { }
341 delete obj.downlinks[p_res.sender_chan];
342 }
343 });
344
345 obj.downlinks[p_res.sender_chan] = chan;
346 } else {
347 // Not a supported port, fail the connection
348 SendChannelOpenFailure(socket.ws, p_res);
349 }
350 return p_res.len;
351 }
352 case APFProtocol.CHANNEL_OPEN_CONFIRMATION: {
353 Debug("APF: CHANNEL_OPEN_CONFIRMATION");
354 return 17;
355 }
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) { }
360 return 5;
361 }
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];
368 if (chan != null) {
369 chan.curInWindow += chan_data_len;
370 try {
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; }
375 });
376 } catch (ex) { Debug("Cannot forward data to downlink socket."); }
377 }
378 return 9 + chan_data_len;
379 }
380 case APFProtocol.CHANNEL_WINDOW_ADJUST: {
381 Debug("APF: CHANNEL_WINDOW_ADJUST");
382 return 9;
383 }
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); } }
388 return 5 + len;
389 }
390 default: {
391 Debug("CMD: " + cmd + " is not implemented.");
392 obj.cirastate = CIRASTATE.FAILED;
393 return 0;
394 }
395 }
396 }
397
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;
411 return result;
412 }
413
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");
417 }
418
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");
422 }
423
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);
427 }
428
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'));
432 }
433
434 function SendChannelClose(socket, chan) {
435 socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan));
436 Debug("APF: Send ChannelClose ");
437 }
438
439 obj.connect = function () {
440 if (obj.forwardClient != null) {
441 try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); }
442 //obj.forwardClient = null;
443 }
444 obj.cirastate = CIRASTATE.INITIAL;
445 obj.pfwd_idx = 0;
446
447 //obj.forwardClient = new obj.ws(obj.args.mpsurl, obj.tlsoptions);
448 //obj.forwardClient.on("open", obj.onSecureConnect);
449
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
455 }
456
457 obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } }
458
459 return obj;
460}
461
462module.exports = CreateAPFClient;