EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
sec.js
Go to the documentation of this file.
1/*
2 * Copyright (c) 2014-2015 Sylvain Peyrefitte
3 *
4 * This file is part of node-rdpjs.
5 *
6 * node-rdpjs is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20var inherits = require('util').inherits;
21var crypto = require('crypto');
22var events = require('events');
23var type = require('../../core').type;
24var error = require('../../core').error;
25var log = require('../../core').log;
26var gcc = require('../t125/gcc');
27var lic = require('./lic');
28var cert = require('../cert');
29var rsa = require('../../security').rsa;
30
31/**
32 * @see http://msdn.microsoft.com/en-us/library/cc240579.aspx
33 */
34var SecurityFlag = {
35 SEC_EXCHANGE_PKT : 0x0001,
36 SEC_TRANSPORT_REQ : 0x0002,
37 RDP_SEC_TRANSPORT_RSP : 0x0004,
38 SEC_ENCRYPT : 0x0008,
39 SEC_RESET_SEQNO : 0x0010,
40 SEC_IGNORE_SEQNO : 0x0020,
41 SEC_INFO_PKT : 0x0040,
42 SEC_LICENSE_PKT : 0x0080,
43 SEC_LICENSE_ENCRYPT_CS : 0x0200,
44 SEC_LICENSE_ENCRYPT_SC : 0x0200,
45 SEC_REDIRECTION_PKT : 0x0400,
46 SEC_SECURE_CHECKSUM : 0x0800,
47 SEC_AUTODETECT_REQ : 0x1000,
48 SEC_AUTODETECT_RSP : 0x2000,
49 SEC_HEARTBEAT : 0x4000,
50 SEC_FLAGSHI_VALID : 0x8000
51};
52
53/**
54 * @see https://msdn.microsoft.com/en-us/library/cc240475.aspx
55 */
56var InfoFlag = {
57 INFO_MOUSE : 0x00000001,
58 INFO_DISABLECTRLALTDEL : 0x00000002,
59 INFO_AUTOLOGON : 0x00000008,
60 INFO_UNICODE : 0x00000010,
61 INFO_MAXIMIZESHELL : 0x00000020,
62 INFO_LOGONNOTIFY : 0x00000040,
63 INFO_COMPRESSION : 0x00000080,
64 INFO_ENABLEWINDOWSKEY : 0x00000100,
65 INFO_REMOTECONSOLEAUDIO : 0x00002000,
66 INFO_FORCE_ENCRYPTED_CS_PDU : 0x00004000,
67 INFO_RAIL : 0x00008000,
68 INFO_LOGONERRORS : 0x00010000,
69 INFO_MOUSE_HAS_WHEEL : 0x00020000,
70 INFO_PASSWORD_IS_SC_PIN : 0x00040000,
71 INFO_NOAUDIOPLAYBACK : 0x00080000,
72 INFO_USING_SAVED_CREDS : 0x00100000,
73 INFO_AUDIOCAPTURE : 0x00200000,
74 INFO_VIDEO_DISABLE : 0x00400000,
75 INFO_CompressionTypeMask : 0x00001E00
76};
77
78/**
79 * @see https://msdn.microsoft.com/en-us/library/cc240476.aspx
80 */
81var AfInet = {
82 AfInet : 0x00002,
83 AF_INET6 : 0x0017
84};
85
86/**
87 * @see https://msdn.microsoft.com/en-us/library/cc240476.aspx
88 */
89var PerfFlag = {
90 PERF_DISABLE_WALLPAPER : 0x00000001,
91 PERF_DISABLE_FULLWINDOWDRAG : 0x00000002,
92 PERF_DISABLE_MENUANIMATIONS : 0x00000004,
93 PERF_DISABLE_THEMING : 0x00000008,
94 PERF_DISABLE_CURSOR_SHADOW : 0x00000020,
95 PERF_DISABLE_CURSORSETTINGS : 0x00000040,
96 PERF_ENABLE_FONT_SMOOTHING : 0x00000080,
97 PERF_ENABLE_DESKTOP_COMPOSITION : 0x00000100
98};
99
100/**
101 * @see http://msdn.microsoft.com/en-us/library/cc241992.aspx
102 * @param input {Buffer} Binary data
103 * @param salt {Buffer} salt for context call
104 * @param salt1 {Buffer} another salt (ex : client random)
105 * @param salt2 {Buffer} another salt (ex : server random)
106 * @return {Buffer}
107 */
108function saltedHash(input, salt, salt1, salt2) {
109 var sha1Digest = crypto.createHash('sha1');
110 sha1Digest.update(input);
111 sha1Digest.update(salt.slice(0, 48));
112 sha1Digest.update(salt1);
113 sha1Digest.update(salt2);
114
115 var sha1Sig = sha1Digest.digest();
116
117 var md5Digest = crypto.createHash('md5');
118 md5Digest.update(salt.slice(0, 48));
119 md5Digest.update(sha1Sig);
120 return md5Digest.digest();
121}
122
123/**
124 * @param key {Buffer} secret
125 * @param random1 {Buffer} client random
126 * @param random2 {Buffer} server random
127 * @returns {Buffer}
128 */
129function finalHash (key, random1, random2) {
130 var md5Digest = crypto.createHash('md5');
131 md5Digest.update(key);
132 md5Digest.update(random1);
133 md5Digest.update(random2);
134 return md5Digest.digest();
135}
136
137/**
138 * @see http://msdn.microsoft.com/en-us/library/cc241992.aspx
139 * @param secret {Buffer} secret
140 * @param random1 {Buffer} client random
141 * @param random2 {Buffer} server random
142 * @returns {Buffer}
143 */
144function masterSecret (secret, random1, random2) {
145 var sh1 = saltedHash(Buffer.from('A'), secret, random1, random2);
146 var sh2 = saltedHash(Buffer.from('BB'), secret, random1, random2);
147 var sh3 = saltedHash(Buffer.from('CCC'), secret, random1, random2);
148
149 var ms = Buffer.alloc(sh1.length + sh2.length + sh3.length);
150 sh1.copy(ms);
151 sh2.copy(ms, sh1.length);
152 sh3.copy(ms, sh1.length + sh2.length);
153 return ms;
154}
155
156/**
157 * @see http://msdn.microsoft.com/en-us/library/cc241995.aspx
158 * @param macSaltKey {Buffer} key
159 * @param data {Buffer} data
160 * @returns {Buffer}
161 */
162function macData(macSaltKey, data) {
163 var salt1 = Buffer.alloc(40);
164 salt1.fill(0x36);
165
166 var salt2 = Buffer.alloc(48);
167 salt2.fill(0x5c);
168
169 var dataLength = new type.UInt32Le(data.length).toStream().buffer;
170
171 var sha1 = crypto.createHash('sha1');
172 sha1.update(macSaltKey);
173 sha1.update(salt1);
174 sha1.update(dataLength);
175 sha1.update(data);
176 var sha1Digest = sha1.digest();
177
178 var md5 = crypto.createHash('md5');
179 md5.update(macSaltKey);
180 md5.update(salt2);
181 md5.update(sha1Digest);
182
183 return md5.digest();
184}
185
186/**
187 * RDP client informations
188 * @param extendedInfoConditional {boolean} true if RDP5+
189 * @returns {type.Component}
190 */
191function rdpInfos(extendedInfoConditional) {
192 var self = {
193 codePage : new type.UInt32Le(),
194 flag : new type.UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY),
195 cbDomain : new type.UInt16Le(function() {
196 return self.domain.size() - 2;
197 }),
198 cbUserName : new type.UInt16Le(function() {
199 return self.userName.size() - 2;
200 }),
201 cbPassword : new type.UInt16Le(function() {
202 return self.password.size() - 2;
203 }),
204 cbAlternateShell : new type.UInt16Le(function() {
205 return self.alternateShell.size() - 2;
206 }),
207 cbWorkingDir : new type.UInt16Le(function() {
208 return self.workingDir.size() - 2;
209 }),
210 domain : new type.BinaryString(Buffer.from('\x00', 'ucs2'),{ readLength : new type.CallableValue(function() {
211 return self.cbDomain.value + 2;
212 })}),
213 userName : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
214 return self.cbUserName.value + 2;
215 })}),
216 password : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function () {
217 return self.cbPassword.value + 2;
218 })}),
219 alternateShell : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
220 return self.cbAlternateShell.value + 2;
221 })}),
222 workingDir : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
223 return self.cbWorkingDir.value + 2;
224 })}),
225 extendedInfo : rdpExtendedInfos({ conditional : extendedInfoConditional })
226 };
227
228 return new type.Component(self);
229}
230
231/**
232 * RDP client extended informations present in RDP5+
233 * @param opt
234 * @returns {type.Component}
235 */
236function rdpExtendedInfos(opt) {
237 var self = {
238 clientAddressFamily : new type.UInt16Le(AfInet.AfInet),
239 cbClientAddress : new type.UInt16Le(function() {
240 return self.clientAddress.size();
241 }),
242 clientAddress : new type.BinaryString(Buffer.from('\x00', 'ucs2'),{ readLength : new type.CallableValue(function() {
243 return self.cbClientAddress;
244 }) }),
245 cbClientDir : new type.UInt16Le(function() {
246 return self.clientDir.size();
247 }),
248 clientDir : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
249 return self.cbClientDir;
250 }) }),
251 clientTimeZone : new type.BinaryString(Buffer.from(Array(172 + 1).join("\x00"))),
252 clientSessionId : new type.UInt32Le(),
253 performanceFlags : new type.UInt32Le()
254 };
255 return new type.Component(self, opt);
256}
257
258/**
259 * Header of security header
260 * @returns {type.Component}
261 */
262function securityHeader() {
263 var self = {
264 securityFlag : new type.UInt16Le(),
265 securityFlagHi : new type.UInt16Le()
266 };
267
268 return new type.Component(self);
269}
270
271/**
272 * Security layer
273 * @param transport {events.EventEmitter}
274 */
275function Sec(transport, fastPathTransport) {
276 this.transport = transport;
277 this.fastPathTransport = fastPathTransport;
278 // init at connect event from transport layer
279 this.gccClient = null;
280 this.gccServer = null;
281 var self = this;
282 this.infos = rdpInfos(function() {
283 return self.gccClient.core.rdpVersion.value === gcc.VERSION.RDP_VERSION_5_PLUS;
284 });
285 this.machineName = '';
286
287
288 // basic encryption
289 this.enableEncryption = false;
290
291 if (this.fastPathTransport) {
292 this.fastPathTransport.on('fastPathData', function (secFlag, s) {
293 self.recvFastPath(secFlag, s);
294 });
295 }
296};
297
298//inherit from Layer
299inherits(Sec, events.EventEmitter);
300
301/**
302 * Send message with security header
303 * @param flag {integer} security flag
304 * @param data {type.*} message
305 */
306Sec.prototype.sendFlagged = function(flag, data) {
307 this.transport.send('global', new type.Component([
308 new type.UInt16Le(flag),
309 new type.UInt16Le(),
310 data
311 ]));
312};
313
314/**
315 * Main send function
316 * @param message {type.*} message to send
317 */
318Sec.prototype.send = function(message) {
319 if (this.enableEncryption) {
320 throw new error.FatalError('NODE_RDP_PROTOCOL_PDU_SEC_ENCRYPT_NOT_IMPLEMENTED');
321 }
322 this.transport.send('global', message);
323};
324
325/**
326 * Main receive function
327 * @param s {type.Stream}
328 */
329Sec.prototype.recv = function(s) {
330 if (this.enableEncryption) {
331 throw new error.FatalError('NODE_RDP_PROTOCOL_PDU_SEC_ENCRYPT_NOT_IMPLEMENTED');
332 }
333 // not support yet basic RDP security layer
334 this.emit('data', s);
335};
336
337/**
338 * Receive fast path data
339 * @param secFlag {integer} security flag
340 * @param s {type.Stream}
341 */
342Sec.prototype.recvFastPath = function (secFlag, s) {
343 // transparent because basic RDP security layer not implemented
344 this.emit('fastPathData', secFlag, s);
345};
346
347/**
348 * Client security layer
349 * @param transport {events.EventEmitter}
350 */
351function Client(transport, fastPathTransport) {
352 Sec.call(this, transport, fastPathTransport);
353 // for basic RDP layer (in futur)
354 this.enableSecureCheckSum = false;
355 var self = this;
356 this.transport.on('connect', function(gccClient, gccServer, userId, channels) {
357 self.connect(gccClient, gccServer, userId, channels);
358 }).on('close', function() {
359 self.emit('close');
360 }).on('error', function (err) {
361 self.emit('error', err);
362 });
363};
364
365//inherit from Layer
366inherits(Client, Sec);
367
368/**
369 * Connect event
370 */
371Client.prototype.connect = function(gccClient, gccServer, userId, channels) {
372 //init gcc information
373 this.gccClient = gccClient;
374 this.gccServer = gccServer;
375 this.userId = userId;
376 this.channelId = channels.find(function(e) {
377 if(e.name === 'global') return true;
378 }).id;
379 this.sendInfoPkt();
380};
381
382/**
383 * close stack
384 */
385Client.prototype.close = function() {
386 this.transport.close();
387};
388
389/**
390 * Send main information packet
391 * VIP (very important packet) because contain credentials
392 */
393Client.prototype.sendInfoPkt = function() {
394 this.sendFlagged(SecurityFlag.SEC_INFO_PKT, this.infos);
395 var self = this;
396 this.transport.once('global', function(s) {
397 self.recvLicense(s);
398 });
399};
400
401function reverse(buffer) {
402 var result = Buffer.alloc(buffer.length);
403 for(var i = 0; i < buffer.length; i++) {
404 result.writeUInt8(buffer.readUInt8(buffer.length - 1 - i), i);
405 }
406 return result;
407}
408
409/**
410 * Send a valid license request
411 * @param licenseRequest {object(lic.serverLicenseRequest)} license requets infos
412 */
413Client.prototype.sendClientNewLicenseRequest = function(licenseRequest) {
414 log.debug('new license request');
415 var serverRandom = licenseRequest.serverRandom.value;
416
417 // read server certificate
418 var s = new type.Stream(licenseRequest.serverCertificate.obj.blobData.value);
419 var certificate = cert.certificate().read(s).obj;
420 var publicKey = certificate.certData.obj.getPublicKey();
421
422 var clientRandom = crypto.randomBytes(32);
423 var preMasterSecret = crypto.randomBytes(48);
424 var mSecret = masterSecret(preMasterSecret, clientRandom, serverRandom);
425 var sessionKeyBlob = masterSecret(mSecret, serverRandom, clientRandom);
426
427 this.licenseMacSalt = sessionKeyBlob.slice(0, 16)
428 this.licenseKey = finalHash(sessionKeyBlob.slice(16, 32), clientRandom, serverRandom);
429
430 var request = lic.clientNewLicenseRequest();
431 request.obj.clientRandom.value = clientRandom;
432
433 var preMasterSecretEncrypted = reverse(rsa.encrypt(reverse(preMasterSecret), publicKey));
434 var preMasterSecretEncryptedPadded = Buffer.alloc(preMasterSecretEncrypted.length + 8);
435 preMasterSecretEncryptedPadded.fill(0);
436 preMasterSecretEncrypted.copy(preMasterSecretEncryptedPadded);
437 request.obj.encryptedPreMasterSecret.obj.blobData.value = preMasterSecretEncryptedPadded;
438
439 request.obj.ClientMachineName.obj.blobData.value = this.infos.obj.userName.value;
440 request.obj.ClientUserName.obj.blobData.value = Buffer.from(this.machineName + '\x00');
441
442 this.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.licensePacket(request));
443};
444
445/**
446 * Send a valid license request
447 * @param platformChallenge {object(lic.serverPlatformChallenge)} platform challenge
448 */
449Client.prototype.sendClientChallengeResponse = function(platformChallenge) {
450 log.debug('challenge license');
451 var serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.obj.blobData.value;
452 var serverChallenge = crypto.createDecipheriv('rc4', this.licenseKey, '').update(serverEncryptedChallenge);
453 if (serverChallenge.toString('ucs2') !== 'TEST\x00') {
454 throw new error.ProtocolError('NODE_RDP_PROTOCOL_PDU_SEC_INVALID_LICENSE_CHALLENGE');
455 }
456
457 var hwid = new type.Component([new type.UInt32Le(2), new type.BinaryString(crypto.randomBytes(16))]).toStream().buffer;
458
459 var response = lic.clientPLatformChallengeResponse();
460 response.obj.encryptedPlatformChallengeResponse.obj.blobData.value = serverEncryptedChallenge;
461 response.obj.encryptedHWID.obj.blobData.value = crypto.createCipheriv('rc4', this.licenseKey, '').update(hwid);
462
463 var sig = Buffer.alloc(serverChallenge.length + hwid.length);
464 serverChallenge.copy(sig);
465 hwid.copy(sig, serverChallenge.length);
466 response.obj.MACData.value = macData(this.licenseMacSalt, sig);
467
468 this.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.licensePacket(response));
469};
470
471/**
472 * Receive license informations
473 * @param s {type.Stream}
474 */
475Sec.prototype.recvLicense = function(s) {
476 var header = securityHeader().read(s).obj;
477 if (!(header.securityFlag.value & SecurityFlag.SEC_LICENSE_PKT)) {
478 throw new error.ProtocolError('NODE_RDP_PROTOCOL_PDU_SEC_BAD_LICENSE_HEADER');
479 }
480
481 var message = lic.licensePacket().read(s).obj;
482 // i'm accepted
483 if (message.bMsgtype.value === lic.MessageType.NEW_LICENSE ||
484 (message.bMsgtype.value === lic.MessageType.ERROR_ALERT
485 && message.licensingMessage.obj.dwErrorCode.value === lic.ErrorCode.STATUS_VALID_CLIENT
486 && message.licensingMessage.obj.dwStateTransition.value === lic.StateTransition.ST_NO_TRANSITION)) {
487 this.emit('connect', this.gccClient.core, this.userId, this.channelId);
488 var self = this;
489 this.transport.on('global', function(s) {
490 self.recv(s);
491 });
492 return;
493 }
494
495 // server ask license request
496 if (message.bMsgtype.value === lic.MessageType.LICENSE_REQUEST) {
497 this.sendClientNewLicenseRequest(message.licensingMessage.obj);
498 }
499
500 // server send challenge
501 if (message.bMsgtype.value === lic.MessageType.PLATFORM_CHALLENGE) {
502 this.sendClientChallengeResponse(message.licensingMessage.obj);
503 }
504
505 var self = this;
506 this.emit('connect', this.gccClient.core);
507 this.transport.once('global', function (s) {
508 self.recvLicense(s);
509 });
510};
511
512/**
513 * Module exports
514 */
515module.exports = {
516 PerfFlag : PerfFlag,
517 InfoFlag : InfoFlag,
518 Client : Client
519};