| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- /*
- * Copyright (c) 2014-2015 Sylvain Peyrefitte
- *
- * This file is part of node-rdpjs.
- *
- * node-rdpjs is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- var inherits = require('util').inherits;
- var crypto = require('crypto');
- var events = require('events');
- var type = require('../../core').type;
- var error = require('../../core').error;
- var log = require('../../core').log;
- var gcc = require('../t125/gcc');
- var lic = require('./lic');
- var cert = require('../cert');
- var rsa = require('../../security').rsa;
- /**
- * @see http://msdn.microsoft.com/en-us/library/cc240579.aspx
- */
- var SecurityFlag = {
- SEC_EXCHANGE_PKT : 0x0001,
- SEC_TRANSPORT_REQ : 0x0002,
- RDP_SEC_TRANSPORT_RSP : 0x0004,
- SEC_ENCRYPT : 0x0008,
- SEC_RESET_SEQNO : 0x0010,
- SEC_IGNORE_SEQNO : 0x0020,
- SEC_INFO_PKT : 0x0040,
- SEC_LICENSE_PKT : 0x0080,
- SEC_LICENSE_ENCRYPT_CS : 0x0200,
- SEC_LICENSE_ENCRYPT_SC : 0x0200,
- SEC_REDIRECTION_PKT : 0x0400,
- SEC_SECURE_CHECKSUM : 0x0800,
- SEC_AUTODETECT_REQ : 0x1000,
- SEC_AUTODETECT_RSP : 0x2000,
- SEC_HEARTBEAT : 0x4000,
- SEC_FLAGSHI_VALID : 0x8000
- };
- /**
- * @see https://msdn.microsoft.com/en-us/library/cc240475.aspx
- */
- var InfoFlag = {
- INFO_MOUSE : 0x00000001,
- INFO_DISABLECTRLALTDEL : 0x00000002,
- INFO_AUTOLOGON : 0x00000008,
- INFO_UNICODE : 0x00000010,
- INFO_MAXIMIZESHELL : 0x00000020,
- INFO_LOGONNOTIFY : 0x00000040,
- INFO_COMPRESSION : 0x00000080,
- INFO_ENABLEWINDOWSKEY : 0x00000100,
- INFO_REMOTECONSOLEAUDIO : 0x00002000,
- INFO_FORCE_ENCRYPTED_CS_PDU : 0x00004000,
- INFO_RAIL : 0x00008000,
- INFO_LOGONERRORS : 0x00010000,
- INFO_MOUSE_HAS_WHEEL : 0x00020000,
- INFO_PASSWORD_IS_SC_PIN : 0x00040000,
- INFO_NOAUDIOPLAYBACK : 0x00080000,
- INFO_USING_SAVED_CREDS : 0x00100000,
- INFO_AUDIOCAPTURE : 0x00200000,
- INFO_VIDEO_DISABLE : 0x00400000,
- INFO_CompressionTypeMask : 0x00001E00
- };
- /**
- * @see https://msdn.microsoft.com/en-us/library/cc240476.aspx
- */
- var AfInet = {
- AfInet : 0x00002,
- AF_INET6 : 0x0017
- };
- /**
- * @see https://msdn.microsoft.com/en-us/library/cc240476.aspx
- */
- var PerfFlag = {
- PERF_DISABLE_WALLPAPER : 0x00000001,
- PERF_DISABLE_FULLWINDOWDRAG : 0x00000002,
- PERF_DISABLE_MENUANIMATIONS : 0x00000004,
- PERF_DISABLE_THEMING : 0x00000008,
- PERF_DISABLE_CURSOR_SHADOW : 0x00000020,
- PERF_DISABLE_CURSORSETTINGS : 0x00000040,
- PERF_ENABLE_FONT_SMOOTHING : 0x00000080,
- PERF_ENABLE_DESKTOP_COMPOSITION : 0x00000100
- };
- /**
- * @see http://msdn.microsoft.com/en-us/library/cc241992.aspx
- * @param input {Buffer} Binary data
- * @param salt {Buffer} salt for context call
- * @param salt1 {Buffer} another salt (ex : client random)
- * @param salt2 {Buffer} another salt (ex : server random)
- * @return {Buffer}
- */
- function saltedHash(input, salt, salt1, salt2) {
- var sha1Digest = crypto.createHash('sha1');
- sha1Digest.update(input);
- sha1Digest.update(salt.slice(0, 48));
- sha1Digest.update(salt1);
- sha1Digest.update(salt2);
-
- var sha1Sig = sha1Digest.digest();
-
- var md5Digest = crypto.createHash('md5');
- md5Digest.update(salt.slice(0, 48));
- md5Digest.update(sha1Sig);
- return md5Digest.digest();
- }
- /**
- * @param key {Buffer} secret
- * @param random1 {Buffer} client random
- * @param random2 {Buffer} server random
- * @returns {Buffer}
- */
- function finalHash (key, random1, random2) {
- var md5Digest = crypto.createHash('md5');
- md5Digest.update(key);
- md5Digest.update(random1);
- md5Digest.update(random2);
- return md5Digest.digest();
- }
- /**
- * @see http://msdn.microsoft.com/en-us/library/cc241992.aspx
- * @param secret {Buffer} secret
- * @param random1 {Buffer} client random
- * @param random2 {Buffer} server random
- * @returns {Buffer}
- */
- function masterSecret (secret, random1, random2) {
- var sh1 = saltedHash(Buffer.from('A'), secret, random1, random2);
- var sh2 = saltedHash(Buffer.from('BB'), secret, random1, random2);
- var sh3 = saltedHash(Buffer.from('CCC'), secret, random1, random2);
-
- var ms = Buffer.alloc(sh1.length + sh2.length + sh3.length);
- sh1.copy(ms);
- sh2.copy(ms, sh1.length);
- sh3.copy(ms, sh1.length + sh2.length);
- return ms;
- }
- /**
- * @see http://msdn.microsoft.com/en-us/library/cc241995.aspx
- * @param macSaltKey {Buffer} key
- * @param data {Buffer} data
- * @returns {Buffer}
- */
- function macData(macSaltKey, data) {
- var salt1 = Buffer.alloc(40);
- salt1.fill(0x36);
-
- var salt2 = Buffer.alloc(48);
- salt2.fill(0x5c);
- var dataLength = new type.UInt32Le(data.length).toStream().buffer;
-
- var sha1 = crypto.createHash('sha1');
- sha1.update(macSaltKey);
- sha1.update(salt1);
- sha1.update(dataLength);
- sha1.update(data);
- var sha1Digest = sha1.digest();
-
- var md5 = crypto.createHash('md5');
- md5.update(macSaltKey);
- md5.update(salt2);
- md5.update(sha1Digest);
-
- return md5.digest();
- }
- /**
- * RDP client informations
- * @param extendedInfoConditional {boolean} true if RDP5+
- * @returns {type.Component}
- */
- function rdpInfos(extendedInfoConditional) {
- var self = {
- codePage : new type.UInt32Le(),
- flag : new type.UInt32Le(InfoFlag.INFO_MOUSE | InfoFlag.INFO_UNICODE | InfoFlag.INFO_LOGONNOTIFY | InfoFlag.INFO_LOGONERRORS | InfoFlag.INFO_DISABLECTRLALTDEL | InfoFlag.INFO_ENABLEWINDOWSKEY),
- cbDomain : new type.UInt16Le(function() {
- return self.domain.size() - 2;
- }),
- cbUserName : new type.UInt16Le(function() {
- return self.userName.size() - 2;
- }),
- cbPassword : new type.UInt16Le(function() {
- return self.password.size() - 2;
- }),
- cbAlternateShell : new type.UInt16Le(function() {
- return self.alternateShell.size() - 2;
- }),
- cbWorkingDir : new type.UInt16Le(function() {
- return self.workingDir.size() - 2;
- }),
- domain : new type.BinaryString(Buffer.from('\x00', 'ucs2'),{ readLength : new type.CallableValue(function() {
- return self.cbDomain.value + 2;
- })}),
- userName : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
- return self.cbUserName.value + 2;
- })}),
- password : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function () {
- return self.cbPassword.value + 2;
- })}),
- alternateShell : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
- return self.cbAlternateShell.value + 2;
- })}),
- workingDir : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
- return self.cbWorkingDir.value + 2;
- })}),
- extendedInfo : rdpExtendedInfos({ conditional : extendedInfoConditional })
- };
-
- return new type.Component(self);
- }
- /**
- * RDP client extended informations present in RDP5+
- * @param opt
- * @returns {type.Component}
- */
- function rdpExtendedInfos(opt) {
- var self = {
- clientAddressFamily : new type.UInt16Le(AfInet.AfInet),
- cbClientAddress : new type.UInt16Le(function() {
- return self.clientAddress.size();
- }),
- clientAddress : new type.BinaryString(Buffer.from('\x00', 'ucs2'),{ readLength : new type.CallableValue(function() {
- return self.cbClientAddress;
- }) }),
- cbClientDir : new type.UInt16Le(function() {
- return self.clientDir.size();
- }),
- clientDir : new type.BinaryString(Buffer.from('\x00', 'ucs2'), { readLength : new type.CallableValue(function() {
- return self.cbClientDir;
- }) }),
- clientTimeZone : new type.BinaryString(Buffer.from(Array(172 + 1).join("\x00"))),
- clientSessionId : new type.UInt32Le(),
- performanceFlags : new type.UInt32Le()
- };
- return new type.Component(self, opt);
- }
- /**
- * Header of security header
- * @returns {type.Component}
- */
- function securityHeader() {
- var self = {
- securityFlag : new type.UInt16Le(),
- securityFlagHi : new type.UInt16Le()
- };
-
- return new type.Component(self);
- }
- /**
- * Security layer
- * @param transport {events.EventEmitter}
- */
- function Sec(transport, fastPathTransport) {
- this.transport = transport;
- this.fastPathTransport = fastPathTransport;
- // init at connect event from transport layer
- this.gccClient = null;
- this.gccServer = null;
- var self = this;
- this.infos = rdpInfos(function() {
- return self.gccClient.core.rdpVersion.value === gcc.VERSION.RDP_VERSION_5_PLUS;
- });
- this.machineName = '';
-
-
- // basic encryption
- this.enableEncryption = false;
-
- if (this.fastPathTransport) {
- this.fastPathTransport.on('fastPathData', function (secFlag, s) {
- self.recvFastPath(secFlag, s);
- });
- }
- };
- //inherit from Layer
- inherits(Sec, events.EventEmitter);
- /**
- * Send message with security header
- * @param flag {integer} security flag
- * @param data {type.*} message
- */
- Sec.prototype.sendFlagged = function(flag, data) {
- this.transport.send('global', new type.Component([
- new type.UInt16Le(flag),
- new type.UInt16Le(),
- data
- ]));
- };
- /**
- * Main send function
- * @param message {type.*} message to send
- */
- Sec.prototype.send = function(message) {
- if (this.enableEncryption) {
- throw new error.FatalError('NODE_RDP_PROTOCOL_PDU_SEC_ENCRYPT_NOT_IMPLEMENTED');
- }
- this.transport.send('global', message);
- };
- /**
- * Main receive function
- * @param s {type.Stream}
- */
- Sec.prototype.recv = function(s) {
- if (this.enableEncryption) {
- throw new error.FatalError('NODE_RDP_PROTOCOL_PDU_SEC_ENCRYPT_NOT_IMPLEMENTED');
- }
- // not support yet basic RDP security layer
- this.emit('data', s);
- };
- /**
- * Receive fast path data
- * @param secFlag {integer} security flag
- * @param s {type.Stream}
- */
- Sec.prototype.recvFastPath = function (secFlag, s) {
- // transparent because basic RDP security layer not implemented
- this.emit('fastPathData', secFlag, s);
- };
- /**
- * Client security layer
- * @param transport {events.EventEmitter}
- */
- function Client(transport, fastPathTransport) {
- Sec.call(this, transport, fastPathTransport);
- // for basic RDP layer (in futur)
- this.enableSecureCheckSum = false;
- var self = this;
- this.transport.on('connect', function(gccClient, gccServer, userId, channels) {
- self.connect(gccClient, gccServer, userId, channels);
- }).on('close', function() {
- self.emit('close');
- }).on('error', function (err) {
- self.emit('error', err);
- });
- };
- //inherit from Layer
- inherits(Client, Sec);
- /**
- * Connect event
- */
- Client.prototype.connect = function(gccClient, gccServer, userId, channels) {
- //init gcc information
- this.gccClient = gccClient;
- this.gccServer = gccServer;
- this.userId = userId;
- this.channelId = channels.find(function(e) {
- if(e.name === 'global') return true;
- }).id;
- this.sendInfoPkt();
- };
- /**
- * close stack
- */
- Client.prototype.close = function() {
- this.transport.close();
- };
- /**
- * Send main information packet
- * VIP (very important packet) because contain credentials
- */
- Client.prototype.sendInfoPkt = function() {
- this.sendFlagged(SecurityFlag.SEC_INFO_PKT, this.infos);
- var self = this;
- this.transport.once('global', function(s) {
- self.recvLicense(s);
- });
- };
- function reverse(buffer) {
- var result = Buffer.alloc(buffer.length);
- for(var i = 0; i < buffer.length; i++) {
- result.writeUInt8(buffer.readUInt8(buffer.length - 1 - i), i);
- }
- return result;
- }
- /**
- * Send a valid license request
- * @param licenseRequest {object(lic.serverLicenseRequest)} license requets infos
- */
- Client.prototype.sendClientNewLicenseRequest = function(licenseRequest) {
- log.debug('new license request');
- var serverRandom = licenseRequest.serverRandom.value;
-
- // read server certificate
- var s = new type.Stream(licenseRequest.serverCertificate.obj.blobData.value);
- var certificate = cert.certificate().read(s).obj;
- var publicKey = certificate.certData.obj.getPublicKey();
-
- var clientRandom = crypto.randomBytes(32);
- var preMasterSecret = crypto.randomBytes(48);
- var mSecret = masterSecret(preMasterSecret, clientRandom, serverRandom);
- var sessionKeyBlob = masterSecret(mSecret, serverRandom, clientRandom);
-
- this.licenseMacSalt = sessionKeyBlob.slice(0, 16)
- this.licenseKey = finalHash(sessionKeyBlob.slice(16, 32), clientRandom, serverRandom);
-
- var request = lic.clientNewLicenseRequest();
- request.obj.clientRandom.value = clientRandom;
-
- var preMasterSecretEncrypted = reverse(rsa.encrypt(reverse(preMasterSecret), publicKey));
- var preMasterSecretEncryptedPadded = Buffer.alloc(preMasterSecretEncrypted.length + 8);
- preMasterSecretEncryptedPadded.fill(0);
- preMasterSecretEncrypted.copy(preMasterSecretEncryptedPadded);
- request.obj.encryptedPreMasterSecret.obj.blobData.value = preMasterSecretEncryptedPadded;
-
- request.obj.ClientMachineName.obj.blobData.value = this.infos.obj.userName.value;
- request.obj.ClientUserName.obj.blobData.value = Buffer.from(this.machineName + '\x00');
-
- this.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.licensePacket(request));
- };
- /**
- * Send a valid license request
- * @param platformChallenge {object(lic.serverPlatformChallenge)} platform challenge
- */
- Client.prototype.sendClientChallengeResponse = function(platformChallenge) {
- log.debug('challenge license');
- var serverEncryptedChallenge = platformChallenge.encryptedPlatformChallenge.obj.blobData.value;
- var serverChallenge = crypto.createDecipheriv('rc4', this.licenseKey, '').update(serverEncryptedChallenge);
- if (serverChallenge.toString('ucs2') !== 'TEST\x00') {
- throw new error.ProtocolError('NODE_RDP_PROTOCOL_PDU_SEC_INVALID_LICENSE_CHALLENGE');
- }
-
- var hwid = new type.Component([new type.UInt32Le(2), new type.BinaryString(crypto.randomBytes(16))]).toStream().buffer;
-
- var response = lic.clientPLatformChallengeResponse();
- response.obj.encryptedPlatformChallengeResponse.obj.blobData.value = serverEncryptedChallenge;
- response.obj.encryptedHWID.obj.blobData.value = crypto.createCipheriv('rc4', this.licenseKey, '').update(hwid);
-
- var sig = Buffer.alloc(serverChallenge.length + hwid.length);
- serverChallenge.copy(sig);
- hwid.copy(sig, serverChallenge.length);
- response.obj.MACData.value = macData(this.licenseMacSalt, sig);
-
- this.sendFlagged(SecurityFlag.SEC_LICENSE_PKT, lic.licensePacket(response));
- };
- /**
- * Receive license informations
- * @param s {type.Stream}
- */
- Sec.prototype.recvLicense = function(s) {
- var header = securityHeader().read(s).obj;
- if (!(header.securityFlag.value & SecurityFlag.SEC_LICENSE_PKT)) {
- throw new error.ProtocolError('NODE_RDP_PROTOCOL_PDU_SEC_BAD_LICENSE_HEADER');
- }
-
- var message = lic.licensePacket().read(s).obj;
- // i'm accepted
- if (message.bMsgtype.value === lic.MessageType.NEW_LICENSE ||
- (message.bMsgtype.value === lic.MessageType.ERROR_ALERT
- && message.licensingMessage.obj.dwErrorCode.value === lic.ErrorCode.STATUS_VALID_CLIENT
- && message.licensingMessage.obj.dwStateTransition.value === lic.StateTransition.ST_NO_TRANSITION)) {
- this.emit('connect', this.gccClient.core, this.userId, this.channelId);
- var self = this;
- this.transport.on('global', function(s) {
- self.recv(s);
- });
- return;
- }
-
- // server ask license request
- if (message.bMsgtype.value === lic.MessageType.LICENSE_REQUEST) {
- this.sendClientNewLicenseRequest(message.licensingMessage.obj);
- }
-
- // server send challenge
- if (message.bMsgtype.value === lic.MessageType.PLATFORM_CHALLENGE) {
- this.sendClientChallengeResponse(message.licensingMessage.obj);
- }
-
- var self = this;
- this.emit('connect', this.gccClient.core);
- this.transport.once('global', function (s) {
- self.recvLicense(s);
- });
- };
- /**
- * Module exports
- */
- module.exports = {
- PerfFlag : PerfFlag,
- InfoFlag : InfoFlag,
- Client : Client
- };
|