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) { 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; }
53
54 // CIRA state
55 var CIRASTATE = {
56 INITIAL: 0,
57 PROTOCOL_VERSION_SENT: 1,
58 AUTH_SERVICE_REQUEST_SENT: 2,
59 AUTH_REQUEST_SENT: 3,
60 PFWD_SERVICE_REQUEST_SENT: 4,
61 GLOBAL_REQUEST_SENT: 5,
62 FAILED: -1
63 }
64 obj.cirastate = CIRASTATE.INITIAL;
65
66 // REDIR state
67 var REDIR_TYPE = {
68 REDIR_UNKNOWN: 0,
69 REDIR_SOL: 1,
70 REDIR_KVM: 2,
71 REDIR_IDER: 3
72 }
73
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);
78
79 // Intel AMT forwarded port list for non-TLS mode
80 //var pfwd_ports = [16992, 623, 16994, 5900];
81 var pfwd_ports = [ 16992, 16993 ];
82
83 // protocol definitions
84 var APFProtocol = {
85 UNKNOWN: 0,
86 DISCONNECT: 1,
87 SERVICE_REQUEST: 5,
88 SERVICE_ACCEPT: 6,
89 USERAUTH_REQUEST: 50,
90 USERAUTH_FAILURE: 51,
91 USERAUTH_SUCCESS: 52,
92 GLOBAL_REQUEST: 80,
93 REQUEST_SUCCESS: 81,
94 REQUEST_FAILURE: 82,
95 CHANNEL_OPEN: 90,
96 CHANNEL_OPEN_CONFIRMATION: 91,
97 CHANNEL_OPEN_FAILURE: 92,
98 CHANNEL_WINDOW_ADJUST: 93,
99 CHANNEL_DATA: 94,
100 CHANNEL_CLOSE: 97,
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.
107 }
108
109 var APFDisconnectCode = {
110 HOST_NOT_ALLOWED_TO_CONNECT: 1,
111 PROTOCOL_ERROR: 2,
112 KEY_EXCHANGE_FAILED: 3,
113 RESERVED: 4,
114 MAC_ERROR: 5,
115 COMPRESSION_ERROR: 6,
116 SERVICE_NOT_AVAILABLE: 7,
117 PROTOCOL_VERSION_NOT_SUPPORTED: 8,
118 HOST_KEY_NOT_VERIFIABLE: 9,
119 CONNECTION_LOST: 10,
120 BY_APPLICATION: 11,
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,
126 BY_POLICY: 17,
127 TEMPORARILY_UNAVAILABLE: 18
128 }
129
130 var APFChannelOpenFailCodes = {
131 ADMINISTRATIVELY_PROHIBITED: 1,
132 CONNECT_FAILED: 2,
133 UNKNOWN_CHANNEL_TYPE: 3,
134 RESOURCE_SHORTAGE: 4,
135 }
136
137 var APFChannelOpenFailureReasonCode = {
138 AdministrativelyProhibited: 1,
139 ConnectFailed: 2,
140 UnknownChannelType: 3,
141 ResourceShortage: 4,
142 }
143
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); }
153 });
154
155 obj.forwardClient.ws.on('data', function (data) {
156 obj.forwardClient.tag.accumulator += hex2rstr(buf2hex(data));
157 try {
158 var len = 0;
159 do {
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();
165 }
166 } while (len > 0);
167 } catch (ex) { Debug(ex); }
168 });
169
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; }
173 });
174
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 }); }
179 }
180 SendProtocolVersion(obj.forwardClient.ws, obj.args.clientuuid);
181 SendServiceRequest(obj.forwardClient.ws, 'auth@amt.intel.com');
182 }
183
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 }); }
188
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);
193 }
194
195 function SendProtocolVersion(socket, uuid) {
196 var data = String.fromCharCode(APFProtocol.PROTOCOLVERSION) + IntToStr(1) + IntToStr(0) + IntToStr(0) + hex2rstr(strToGuid(uuid)) + binzerostring(64);
197 socket.write(data);
198 Debug("APF: Send protocol version 1 0 " + uuid);
199 obj.cirastate = CIRASTATE.PROTOCOL_VERSION_SENT;
200 }
201
202 function SendServiceRequest(socket, service) {
203 var data = String.fromCharCode(APFProtocol.SERVICE_REQUEST) + IntToStr(service.length) + service;
204 socket.write(data);
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;
210 }
211 }
212
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;
216 //password auth
217 data += IntToStr(8) + 'password';
218 data += binzerostring(1) + IntToStr(pass.length) + pass;
219 socket.write(data);
220 Debug("APF: Send username password authentication to MPS");
221 obj.cirastate = CIRASTATE.AUTH_REQUEST_SENT;
222 }
223
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);
228 socket.write(data);
229 Debug("APF: Send tcpip-forward " + amthostname + ":" + amtport);
230 obj.cirastate = CIRASTATE.GLOBAL_REQUEST_SENT;
231 }
232
233 function SendKeepAliveRequest(socket) {
234 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REQUEST) + IntToStr(255));
235 Debug("APF: Send keepalive request");
236 }
237
238 function SendKeepAliveReply(socket, cookie) {
239 socket.write(String.fromCharCode(APFProtocol.KEEPALIVE_REPLY) + IntToStr(cookie));
240 Debug("APF: Send keepalive reply");
241 }
242
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; }
248
249 // Respond to MPS according to obj.cirastate
250 switch (cmd) {
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);
257 }
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++]);
261 }
262 }
263 return 5 + slen;
264 }
265 case APFProtocol.REQUEST_SUCCESS: {
266 if (len >= 5) {
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++]);
272 } else {
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);//
278 }
279 return 5;
280 }
281 Debug("APF: Request successful.");
282 return 1;
283 }
284 case APFProtocol.USERAUTH_SUCCESS: {
285 Debug("APF: User Authentication successful");
286 // Send Pfwd service request
287 SendServiceRequest(socket.ws, 'pfwd@amt.intel.com');
288 return 1;
289 }
290 case APFProtocol.USERAUTH_FAILURE: {
291 Debug("APF: User Authentication failed");
292 obj.cirastate = CIRASTATE.FAILED;
293 return 14;
294 }
295 case APFProtocol.KEEPALIVE_REQUEST: {
296 Debug("APF: Keep Alive Request with cookie: " + ReadInt(data, 1));
297 SendKeepAliveReply(socket.ws, ReadInt(data, 1));
298 return 5;
299 }
300 case APFProtocol.KEEPALIVE_REPLY: {
301 Debug("APF: Keep Alive Reply with cookie: " + ReadInt(data, 1));
302 return 5;
303 }
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);
316 });
317
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;
321
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);
326 });
327
328 chan.on('error', function (e) {
329 //Debug("Downlink connection error: " + e);
330 SendChannelOpenFailure(socket.ws, p_res);
331 });
332
333 chan.on('end', function () {
334 var chan = obj.downlinks[p_res.sender_chan];
335 if (chan != null) {
336 Debug("Socket ends.");
337 try { SendChannelClose(socket.ws, p_res.sender_chan); } catch (ex) { }
338 delete obj.downlinks[p_res.sender_chan];
339 }
340 });
341
342 obj.downlinks[p_res.sender_chan] = chan;
343 } else {
344 // Not a supported port, fail the connection
345 SendChannelOpenFailure(socket.ws, p_res);
346 }
347 return p_res.len;
348 }
349 case APFProtocol.CHANNEL_OPEN_CONFIRMATION: {
350 Debug("APF: CHANNEL_OPEN_CONFIRMATION");
351 return 17;
352 }
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) { }
357 return 5;
358 }
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];
365 if (chan != null) {
366 chan.curInWindow += chan_data_len;
367 try {
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; }
372 });
373 } catch (ex) { Debug("Cannot forward data to downlink socket."); }
374 }
375 return 9 + chan_data_len;
376 }
377 case APFProtocol.CHANNEL_WINDOW_ADJUST: {
378 Debug("APF: CHANNEL_WINDOW_ADJUST");
379 return 9;
380 }
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); } }
385 return 5 + len;
386 }
387 default: {
388 Debug("CMD: " + cmd + " is not implemented.");
389 obj.cirastate = CIRASTATE.FAILED;
390 return 0;
391 }
392 }
393 }
394
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;
408 return result;
409 }
410
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");
414 }
415
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");
419 }
420
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);
424 }
425
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'));
429 }
430
431 function SendChannelClose(socket, chan) {
432 socket.write(String.fromCharCode(APFProtocol.CHANNEL_CLOSE) + IntToStr(chan));
433 Debug("APF: Send ChannelClose ");
434 }
435
436 obj.connect = function () {
437 if (obj.forwardClient != null) {
438 try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); }
439 //obj.forwardClient = null;
440 }
441 obj.cirastate = CIRASTATE.INITIAL;
442 obj.pfwd_idx = 0;
443
444 //obj.forwardClient = new obj.ws(obj.args.mpsurl, obj.tlsoptions);
445 //obj.forwardClient.on("open", obj.onSecureConnect);
446
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
452 }
453
454 obj.disconnect = function () { try { obj.forwardClient.ws.end(); } catch (ex) { Debug(ex); } }
455
456 return obj;
457}
458
459module.exports = CreateAPFClient;