1
0
Fork 0
mirror of https://github.com/Ylianst/MeshCentral.git synced 2025-03-09 15:40:18 +00:00

Added Web based RDP support with NLA, #3867 and #3914

This commit is contained in:
Ylian Saint-Hilaire 2022-04-29 11:13:58 -07:00
parent f88d3063fe
commit db06ec1975
37 changed files with 28174 additions and 44 deletions

175
rdp/protocol/cert.js Normal file
View file

@ -0,0 +1,175 @@
/*
* 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 type = require('../core').type;
var log = require('../core').log;
var x509 = require('../security').x509;
var rsa = require('../security').rsa;
var asn1 = require('../asn1');
/**
*  @see http://msdn.microsoft.com/en-us/library/cc240521.aspx
*/
var CertificateType = {
CERT_CHAIN_VERSION_1 : 0x00000001,
CERT_CHAIN_VERSION_2 : 0x00000002
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240520.aspx
* @returns
*/
function rsaPublicKey(opt) {
var self = {
magic : new type.UInt32Le(0x31415352, { constant : true }),
keylen : new type.UInt32Le(function() {
return self.modulus.size() + self.paddinf.size();
}),
bitlen : new type.UInt32Le(function() {
return (self.keylen.value - 8) * 8;
}),
datalen : new type.UInt32Le(function() {
return (self.bitlen.value / 8) - 1;
}),
pubExp : new type.UInt32Le(),
modulus : new type.BinaryString(null, { readLength : new type.CallableValue(function() {
return self.keylen.value - 8;
}) }),
padding : new type.BinaryString(Buffer.from(Array(8 + 1).join('\x00')), { readLength : new type.CallableValue(8) })
};
return new type.Component(self, opt);
}
/**
* http://msdn.microsoft.com/en-us/library/cc240519.aspx
* @returns {type.Component}
*/
function proprietaryCertificate() {
var self = {
__TYPE__ : CertificateType.CERT_CHAIN_VERSION_1,
dwSigAlgId : new type.UInt32Le(0x00000001, { constant : true }),
dwKeyAlgId : new type.UInt32Le(0x00000001, { constant : true }),
wPublicKeyBlobType : new type.UInt16Le(0x0006, { constant : true }),
wPublicKeyBlobLen : new type.UInt16Le(function() {
return self.PublicKeyBlob.size();
}),
PublicKeyBlob : rsaPublicKey({ readLength : new type.CallableValue(function() {
return self.wPublicKeyBlobLen.value;
}) }),
wSignatureBlobType : new type.UInt16Le(0x0008, { constant : true }),
wSignatureBlobLen : new type.UInt16Le(function() {
return self.SignatureBlob.size() + self.padding.size();
}),
SignatureBlob : new type.BinaryString(null, { readLength : new type.CallableValue(function() {
return self.wSignatureBlobLen.value - self.padding.size;
}) }),
padding : new type.BinaryString(Array(8 + 1).join('\x00'), { readLength : new type.CallableValue(8) }),
/**
* @return {object} rsa.publicKey
*/
getPublicKey : function() {
return rsa.publicKey(self.PublicKeyBlob.obj.modulus.value, self.PublicKeyBlob.obj.pubExp.value);
}
};
return new type.Component(self);
}
/**
* For x509 certificate
* @see http://msdn.microsoft.com/en-us/library/cc241911.aspx
* @returns {type.Component}
*/
function certBlob() {
var self = {
cbCert : new type.UInt32Le(function() {
return self.abCert.size();
}),
abCert : new type.BinaryString(null, { readLength : new type.CallableValue(function() {
return self.cbCert.value;
}) })
};
return new type.Component(self);
}
/**
* x509 certificate chain
* @see http://msdn.microsoft.com/en-us/library/cc241910.aspx
* @returns {type.Component}
*/
function x509CertificateChain() {
var self = {
__TYPE__ : CertificateType.CERT_CHAIN_VERSION_2,
NumCertBlobs : new type.UInt32Le(),
CertBlobArray : new type.Factory(function(s) {
self.CertBlobArray = new type.Component([]);
for(var i = 0; i < self.NumCertBlobs.value; i++) {
self.CertBlobArray.obj.push(certBlob().read(s));
}
}),
padding : new type.BinaryString(null, { readLength : new type.CallableValue(function() {
return 8 + 4 * self.NumCertBlobs.value;
}) }),
/**
* @return {object} {n : modulus{bignum}, e : publicexponent{integer}
*/
getPublicKey : function(){
var cert = x509.X509Certificate().decode(new type.Stream(self.CertBlobArray.obj[self.CertBlobArray.obj.length - 1].obj.abCert.value), asn1.ber);
var publikeyStream = new type.Stream(cert.value.tbsCertificate.value.subjectPublicKeyInfo.value.subjectPublicKey.toBuffer());
var asn1PublicKey = x509.RSAPublicKey().decode(publikeyStream, asn1.ber);
return rsa.publicKey(asn1PublicKey.value.modulus.value, asn1PublicKey.value.publicExponent.value);
}
};
return new type.Component(self);
}
function certificate() {
var self = {
dwVersion : new type.UInt32Le(function() {
return self.certData.__TYPE__;
}),
certData : new type.Factory(function(s) {
switch(self.dwVersion.value & 0x7fffffff) {
case CertificateType.CERT_CHAIN_VERSION_1:
log.debug('read proprietary certificate');
self.certData = proprietaryCertificate().read(s);
break;
case CertificateType.CERT_CHAIN_VERSION_2:
log.debug('read x.509 certificate chain');
self.certData = x509CertificateChain().read(s);
break;
default:
log.error('unknown cert type ' + self.dwVersion.value & 0x7fffffff);
}
})
};
return new type.Component(self);
}
/**
* Module exports
*/
module.exports = {
CertificateType : CertificateType,
certificate : certificate
};

30
rdp/protocol/index.js Normal file
View file

@ -0,0 +1,30 @@
/*
* 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 TPKT = require('./tpkt');
var x224 = require('./x224');
var t125 = require('./t125');
var rdp = require('./rdp');
module.exports = {
TPKT : TPKT,
x224 : x224,
t125 : t125,
rdp : rdp
};

701
rdp/protocol/nla.js Normal file
View file

@ -0,0 +1,701 @@
const inherits = require('util').inherits;
const type = require('../core').type;
const events = require('events');
const crypto = require('crypto');
const forge = require('node-forge');
const asn1 = forge.asn1;
const pki = forge.pki;
/**
* NLA layer of rdp stack
*/
function NLA(transport, nlaCompletedFunc, domain, user, password) {
// Get NTLM ready
const ntlm = Create_Ntlm();
ntlm.domain = domain;
ntlm.completedFunc = nlaCompletedFunc;
ntlm.user = user;
ntlm.password = password;
ntlm.response_key_nt = ntowfv2(ntlm.password, ntlm.user, ntlm.domain);
ntlm.response_key_lm = lmowfv2(ntlm.password, ntlm.user, ntlm.domain);
this.ntlm = ntlm;
this.state = 1;
// Get transport ready
this.transport = transport;
// Wait 2 bytes
this.transport.expect(2);
// Next state is receive header
var self = this;
this.oldDataListeners = this.transport.listeners('data');
this.oldCloseListeners = this.transport.listeners('close');
this.oldErrorListeners = this.transport.listeners('error');
// Unhook the previous transport handler
this.transport.removeAllListeners('data');
this.transport.removeAllListeners('close');
this.transport.removeAllListeners('error');
// Hook this module as the transport handler
this.transport.once('data', function (s) {
self.recvHeader(s);
}).on('close', function () {
self.emit('close');
}).on('error', function (err) {
self.emit('close'); // Errors occur when NLA authentication fails, for now, just close.
//self.emit('error', err);
});
}
/**
* inherit from a packet layer
*/
inherits(NLA, events.EventEmitter);
/**
* Receive correct packet as expected
* @param s {type.Stream}
*/
NLA.prototype.recvHeader = function (s) {
//console.log('NLA - recvHeader', s);
var self = this;
var derType = new type.UInt8().read(s).value;
var derLen = new type.UInt8().read(s).value;
self.buffers = [ s.buffer ];
if (derLen < 128) {
// wait for the entire data block
this.transport.expect(derLen);
this.transport.once('data', function (s) { self.recvData(s); });
} else {
// wait for the header size
this.transport.expect(derLen - 128);
this.transport.once('data', function (s) { self.recvHeaderSize(s); });
}
//console.log('NLA - DER', derType, derLen);
};
/**
* Receive correct packet as expected
* @param s {type.Stream}
*/
NLA.prototype.recvHeaderSize = function (s) {
//console.log('NLA - recvHeaderSize', s.buffer.length);
var self = this;
self.buffers.push(s.buffer);
if (s.buffer.length == 1) {
// wait for the entire data block
var derLen = s.buffer.readUInt8(0);
this.transport.expect(derLen);
this.transport.once('data', function (s) { self.recvData(s); });
} else if (s.buffer.length == 2) {
// wait for the entire data block
var derLen = s.buffer.readUInt16BE(0);
this.transport.expect(derLen);
this.transport.once('data', function (s) { self.recvData(s); });
}
}
/**
* Receive correct packet as expected
* @param s {type.Stream}
*/
NLA.prototype.recvData = function (s) {
//console.log('NLA - recvData', s.buffer.length);
var self = this;
self.buffers.push(s.buffer);
var entireBuffer = Buffer.concat(self.buffers);
//console.log('entireBuffer', entireBuffer.toString('hex'));
// We have a full ASN1 data block, decode it now
const der = asn1.fromDer(entireBuffer.toString('binary'));
const derNum = der.value[0].value[0].value.charCodeAt(0);
//console.log('NLA - Number', derNum);
if (derNum == 6) {
if (this.state == 1) {
const derBuffer = Buffer.from(der.value[1].value[0].value[0].value[0].value[0].value, 'binary');
const client_challenge = read_challenge_message(this.ntlm, derBuffer);
self.security_interface = build_security_interface(this.ntlm);
const peer_cert = this.transport.secureSocket.getPeerCertificate();
const challenge = create_ts_authenticate(client_challenge, self.security_interface.gss_wrapex(peer_cert.pubkey.slice(24)));
this.ntlm.publicKeyDer = peer_cert.pubkey.slice(24);
this.send(challenge);
this.state = 2;
} else if (this.state == 2) {
const derBuffer = Buffer.from(der.value[1].value[0].value, 'binary');
const publicKeyDer = self.security_interface.gss_unwrapex(derBuffer);
// Check that the public key is identical except the first byte which is the DER encoding type.
if (!this.ntlm.publicKeyDer.slice(1).equals(publicKeyDer.slice(1))) { console.log('RDP man-in-the-middle detected.'); close(); return; }
delete this.ntlm.publicKeyDer; // Clean this up, we don't need it anymore.
var xdomain, xuser, xpassword;
if (this.ntlm.is_unicode) {
xdomain = toUnicode(this.ntlm.domain);
xuser = toUnicode(this.ntlm.user);
xpassword = toUnicode(this.ntlm.password);
} else {
xdomain = Buffer.from(this.ntlm.domain, 'utf8');
xuser = Buffer.from(this.ntlm.user, 'utf8');
xpassword = Buffer.from(this.ntlm.password, 'utf8');
}
const credentials = create_ts_authinfo(self.security_interface.gss_wrapex(create_ts_credentials(xdomain, xuser, xpassword)));
this.send(credentials);
// Rehook the previous transport handler
this.transport.removeAllListeners('data');
this.transport.removeAllListeners('close');
this.transport.removeAllListeners('error');
for (var i in this.oldDataListeners) { this.transport.once('data', this.oldDataListeners[i]); }
for (var i in this.oldCloseListeners) { this.transport.on('close', this.oldCloseListeners[i]); }
for (var i in this.oldErrorListeners) { this.transport.on('error', this.oldErrorListeners[i]); }
// Done!
this.transport.expect(2);
this.state = 3;
this.ntlm.completedFunc();
return;
}
}
// Receive next block of data
this.transport.expect(2);
this.transport.once('data', function (s) { self.recvHeader(s); });
}
/**
* Send message throught NLA layer
* @param message {type.*}
*/
NLA.prototype.send = function (message) {
this.transport.sendBuffer(message);
};
/**
* close stack
*/
NLA.prototype.close = function() {
this.transport.close();
};
NLA.prototype.sendNegotiateMessage = function () {
// Create create_ts_request
this.ntlm.negotiate_message = create_negotiate_message();
const asn1obj =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, asn1.integerToDer(2)),
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, this.ntlm.negotiate_message.toString('binary'))
])
])
])
])
]);
// Serialize an ASN.1 object to DER format
this.send(Buffer.from(asn1.toDer(asn1obj).data, 'binary'));
}
/**
* Module exports
*/
module.exports = NLA;
const NegotiateFlags = {
NtlmsspNegociate56: 0x80000000,
NtlmsspNegociateKeyExch: 0x40000000,
NtlmsspNegociate128: 0x20000000,
NtlmsspNegociateVersion: 0x02000000,
NtlmsspNegociateTargetInfo: 0x00800000,
NtlmsspRequestNonNTSessionKey: 0x00400000,
NtlmsspNegociateIdentify: 0x00100000,
NtlmsspNegociateExtendedSessionSecurity: 0x00080000,
NtlmsspTargetTypeServer: 0x00020000,
NtlmsspTargetTypeDomain: 0x00010000,
NtlmsspNegociateAlwaysSign: 0x00008000,
NtlmsspNegociateOEMWorkstationSupplied: 0x00002000,
NtlmsspNegociateOEMDomainSupplied: 0x00001000,
NtlmsspNegociateNTLM: 0x00000200,
NtlmsspNegociateLMKey: 0x00000080,
NtlmsspNegociateDatagram: 0x00000040,
NtlmsspNegociateSeal: 0x00000020,
NtlmsspNegociateSign: 0x00000010,
NtlmsspRequestTarget: 0x00000004,
NtlmNegotiateOEM: 0x00000002,
NtlmsspNegociateUnicode: 0x00000001
}
const MajorVersion = {
WindowsMajorVersion5: 0x05,
WindowsMajorVersion6: 0x06
}
const MinorVersion = {
WindowsMinorVersion0: 0x00,
WindowsMinorVersion1: 0x01,
WindowsMinorVersion2: 0x02,
WindowsMinorVersion3: 0x03
}
const NTLMRevision = {
NtlmSspRevisionW2K3: 0x0F
}
function decodeTargetInfo(targetInfoBuf) {
var r = {}, type, len, data, ptr = 0;
while (true) {
type = targetInfoBuf.readInt16LE(ptr);
if (type == 0) break;
len = targetInfoBuf.readInt16LE(ptr + 2);
r[type] = targetInfoBuf.slice(ptr + 4, ptr + 4 + len);
ptr += (4 + len);
}
return r;
}
function bufToArr(b) { var r = []; for (var i = 0; i < b.length; i++) { r.push(b.readUInt8(i)); } return r; } // For unit testing
function compareArray(a, b) { if (a.length != b.length) return false; for (var i = 0; i < a.length; i++) { if (a[i] != b[i]) return false; } return true; } // For unit testing
function toUnicode(str) { return Buffer.from(str, 'ucs2'); }
function md4(str) { return crypto.createHash('md4').update(str).digest(); }
function md5(str) { return crypto.createHash('md5').update(str).digest(); }
function hmac_md5(key, data) { return crypto.createHmac('md5', key).update(data).digest(); }
function ntowfv2(password, user, domain) { return hmac_md5(md4(toUnicode(password)), toUnicode(user.toUpperCase() + domain)); }
function lmowfv2(password, user, domain) { return ntowfv2(password, user, domain); }
function zeroBuffer(len) { return Buffer.alloc(len); }
function compute_response_v2(response_key_nt, response_key_lm, server_challenge, client_challenge, time, server_name) {
const response_version = Buffer.from('01', 'hex');
const hi_response_version = Buffer.from('01', 'hex');
const temp = Buffer.concat([response_version, hi_response_version, zeroBuffer(6), time, client_challenge, zeroBuffer(4), server_name]);
const nt_proof_str = hmac_md5(response_key_nt, Buffer.concat([server_challenge, temp]));
const nt_challenge_response = Buffer.concat([nt_proof_str, temp]);
const lm_challenge_response = Buffer.concat([hmac_md5(response_key_lm, Buffer.concat([server_challenge, client_challenge])), client_challenge]);
const session_base_key = hmac_md5(response_key_nt, nt_proof_str);
return [nt_challenge_response, lm_challenge_response, session_base_key];
}
function kx_key_v2(session_base_key, _lm_challenge_response, _server_challenge) { return session_base_key; }
function rc4k(key, data) { return crypto.createCipheriv('rc4', key, null).update(data); }
function create_negotiate_message() {
return negotiate_message(
NegotiateFlags.NtlmsspNegociateKeyExch |
NegotiateFlags.NtlmsspNegociate128 |
NegotiateFlags.NtlmsspNegociateExtendedSessionSecurity |
NegotiateFlags.NtlmsspNegociateAlwaysSign |
NegotiateFlags.NtlmsspNegociateNTLM |
NegotiateFlags.NtlmsspNegociateSeal |
NegotiateFlags.NtlmsspNegociateSign |
NegotiateFlags.NtlmsspRequestTarget |
NegotiateFlags.NtlmsspNegociateUnicode, Buffer.alloc(0), Buffer.alloc(0)
);
}
function negotiate_message(flags, domain, workstation) {
const offset = ((flags & NegotiateFlags.NtlmsspNegociateVersion) == 0) ? 32 : 40;
const buf = Buffer.alloc(offset);
buf.write('4e544c4d53535000', 0, 8, 'hex'); // Signature (NTLMSP\0)
buf.writeInt32LE(1, 8); // MessageType (1)
buf.writeInt32LE(flags, 12); // Flags
buf.writeInt16LE(domain.length, 16); // DomainNameLen
buf.writeInt16LE(domain.length, 18); // DomainNameMaxLen
if (domain.length > 0) { buf.writeInt32LE(offset, 20); } // DomainNameBufferOffset
buf.writeInt16LE(workstation.length, 24); // WorkstationLen
buf.writeInt16LE(workstation.length, 26); // WorkstationMaxLen
if (workstation.length > 0) { buf.writeInt32LE(offset + domain.length, 28); } // WorkstationBufferOffset
if ((flags & NegotiateFlags.NtlmsspNegociateVersion) != 0) {
buf.writeUInt8(MajorVersion.WindowsMajorVersion6, 32); // ProductMajorVersion
buf.writeUInt8(MinorVersion.WindowsMinorVersion0, 33); // ProductMinorVersion
buf.writeInt16LE(6002, 34); // ProductBuild
//buf.writeInt16LE(0, 36); // Reserved
//buf.writeUInt8(0, 38); // Reserved
buf.writeUInt8(NTLMRevision.NtlmSspRevisionW2K3, 39); // NTLMRevisionCurrent
}
return Buffer.concat([buf, domain, workstation]);
}
function mac(rc4_handle, signing_key, seq_num, data) {
const buf = Buffer.alloc(4);
buf.writeInt32LE(seq_num, 0);
var signature = hmac_md5(signing_key, Buffer.concat([buf, data]));
return message_signature_ex(rc4_handle.update(signature.slice(0, 8)), seq_num);
}
function message_signature_ex(check_sum, seq_num) {
const buf = Buffer.alloc(16);
buf.writeInt32LE(1, 0); // Version
if (check_sum) { check_sum.copy(buf, 4, 0, 8); } // check_sum
if (seq_num) { buf.writeInt32LE(seq_num, 12); } // seq_num
return buf;
}
/// Compute a signature of all data exchange during NTLMv2 handshake
function mic(exported_session_key, negotiate_message, challenge_message, authenticate_message) { return hmac_md5(exported_session_key, Buffer.concat([negotiate_message, challenge_message, authenticate_message])); }
/// NTLMv2 security interface generate a sign key
/// By using MD5 of the session key + a static member (sentense)
function sign_key(exported_session_key, is_client) {
if (is_client) {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server signing key magic constant\0")]));
} else {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client signing key magic constant\0")]));
}
}
/// NTLMv2 security interface generate a seal key
/// By using MD5 of the session key + a static member (sentense)
function seal_key(exported_session_key, is_client) {
if (is_client) {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to client-to-server sealing key magic constant\0")]));
} else {
return md5(Buffer.concat([exported_session_key, Buffer.from("session key to server-to-client sealing key magic constant\0")]));
}
}
/// We are now able to build a security interface
/// that will be used by the CSSP manager to cipherring message (private keys)
/// To detect MITM attack
function build_security_interface(ntlm) {
const obj = {};
if (ntlm) {
obj.signing_key = sign_key(ntlm.exported_session_key, true);
obj.verify_key = sign_key(ntlm.exported_session_key, false);
const client_sealing_key = seal_key(ntlm.exported_session_key, true);
const server_sealing_key = seal_key(ntlm.exported_session_key, false);
obj.encrypt = crypto.createCipheriv('rc4', client_sealing_key, null);
obj.decrypt = crypto.createCipheriv('rc4', server_sealing_key, null);
}
obj.seq_num = 0;
obj.gss_wrapex = function (data) {
const encrypted_data = obj.encrypt.update(data);
const signature = mac(obj.encrypt, obj.signing_key, obj.seq_num, data);
obj.seq_num++;
return Buffer.concat([signature, encrypted_data]);
}
obj.gss_unwrapex = function (data) {
const version = data.readInt32LE(0);
const checksum = data.slice(4, 12);
const seqnum = data.readInt32LE(12);
const payload = data.slice(16);
const plaintext_payload = obj.decrypt.update(payload);
const plaintext_checksum = obj.decrypt.update(checksum);
const seqnumbuf = Buffer.alloc(4);
seqnumbuf.writeInt32LE(seqnum, 0);
const computed_checksum = hmac_md5(obj.verify_key, Buffer.concat([seqnumbuf, plaintext_payload])).slice(0, 8);
if (!plaintext_checksum.equals(computed_checksum)) { console.log("Invalid checksum on NTLMv2"); }
return plaintext_payload;
}
return obj;
}
function Create_Ntlm() {
return {
/// Microsoft Domain for Active Directory
domain: "", //String,
/// Username
user: "", //String,
/// Password
password: "", // String,
/// Key generated from NTLM hash
response_key_nt: null, // Buffer
/// Key generated from NTLM hash
response_key_lm: null, // Buffer
/// Keep trace of each messages to compute a final hash
negotiate_message: null, // Buffer
/// Key use to ciphering messages
exported_session_key: crypto.randomBytes(16), // Buffer
/// True if session use unicode
is_unicode: false // Boolean
}
}
function authenticate_message(lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key, flags) {
const payload = Buffer.concat([lm_challenge_response, nt_challenge_response, domain, user, workstation, encrypted_random_session_key]);
const offset = ((flags & NegotiateFlags.NtlmsspNegociateVersion) == 0) ? 80 : 88;
const buf = Buffer.alloc(offset - 16);
buf.write('4e544c4d53535000', 0, 8, 'hex'); // Signature
buf.writeInt32LE(3, 8); // MessageType
buf.writeInt16LE(lm_challenge_response.length, 12); // LmChallengeResponseLen
buf.writeInt16LE(lm_challenge_response.length, 14); // LmChallengeResponseMaxLen
buf.writeInt32LE(offset, 16); // LmChallengeResponseBufferOffset
buf.writeInt16LE(nt_challenge_response.length, 20); // NtChallengeResponseLen
buf.writeInt16LE(nt_challenge_response.length, 22); // NtChallengeResponseMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length, 24); // NtChallengeResponseBufferOffset
buf.writeInt16LE(domain.length, 28); // DomainNameLen
buf.writeInt16LE(domain.length, 30); // DomainNameMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length, 32); // DomainNameBufferOffset
buf.writeInt16LE(user.length, 36); // UserNameLen
buf.writeInt16LE(user.length, 38); // UserNameMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length, 40); // UserNameBufferOffset
buf.writeInt16LE(workstation.length, 44); // WorkstationLen
buf.writeInt16LE(workstation.length, 46); // WorkstationMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length, 48); // WorkstationBufferOffset
buf.writeInt16LE(encrypted_random_session_key.length, 52); // EncryptedRandomSessionLen
buf.writeInt16LE(encrypted_random_session_key.length, 54); // EncryptedRandomSessionMaxLen
buf.writeInt32LE(offset + lm_challenge_response.length + nt_challenge_response.length + domain.length + user.length + workstation.length, 56); // EncryptedRandomSessionBufferOffset
buf.writeInt32LE(flags, 60); // NegotiateFlags
if ((flags & NegotiateFlags.NtlmsspNegociateVersion) != 0) {
buf.writeUInt8(MajorVersion.WindowsMajorVersion6, 64); // ProductMajorVersion
buf.writeUInt8(MinorVersion.WindowsMinorVersion0, 65); // ProductMinorVersion
buf.writeInt16LE(6002, 66); // ProductBuild
//buf.writeInt16LE(0, 68); // Reserved
//buf.writeUInt8(0, 70); // Reserved
buf.writeUInt8(NTLMRevision.NtlmSspRevisionW2K3, 71); // NTLMRevisionCurrent
}
return [buf, payload];
}
function create_ts_authinfo(auth_info) {
asn1obj =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, asn1.integerToDer(2)),
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, auth_info.toString('binary'))
])
]);
return Buffer.from(asn1.toDer(asn1obj).data, 'binary');
}
function create_ts_credentials(domain, user, password) {
var asn1obj =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, domain.toString('binary'))
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, user.toString('binary'))
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, password.toString('binary'))
])
]);
const ts_password_cred_encoded = asn1.toDer(asn1obj).data;
asn1obj =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, asn1.integerToDer(1)),
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, ts_password_cred_encoded)
])
]);
return Buffer.from(asn1.toDer(asn1obj).data, 'binary');
}
function create_ts_authenticate(nego, pub_key_auth) {
// Create create_ts_request
const asn1obj =
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, asn1.integerToDer(2)),
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, nego.toString('binary'))
])
])
])
]),
asn1.create(asn1.Class.CONTEXT_SPECIFIC, 3, true, [
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, pub_key_auth.toString('binary'))
]),
]);
// Serialize an ASN.1 object to DER format
return Buffer.from(asn1.toDer(asn1obj).data, 'binary');
}
function read_challenge_message(ntlm, derBuffer) {
//console.log('ntlm.negotiate_message', ntlm.negotiate_message.toString('hex'));
//ntlm.negotiate_message = Buffer.from('4e544c4d53535000010000003582086000000000000000000000000000000000', 'hex');
// ********
//ntlm.exported_session_key = Buffer.from('9a1ed052e932834a311daf90c2750219', 'hex'); // *************************
//derBuffer = Buffer.from('4e544c4d53535000020000000e000e003800000035828a6259312ef59a4517dd000000000000000058005800460000000a00614a0000000f430045004e005400520041004c0002000e00430045004e005400520041004c0001000e00430045004e005400520041004c0004000e00430065006e007400720061006c0003000e00430065006e007400720061006c00070008007b7b3bee9e5ad80100000000', 'hex');
//console.log("YST: read_challenge_message1: ", derBuffer.toString('hex'));
const headerSignature = derBuffer.slice(0, 8);
if (headerSignature.toString('hex') != '4e544c4d53535000') { console.log('BAD SIGNATURE'); }
const messageType = derBuffer.readInt32LE(8);
if (messageType != 2) { console.log('BAD MESSAGE TYPE'); }
const targetNameLen = derBuffer.readInt16LE(12);
const targetNameLenMax = derBuffer.readInt16LE(14);
const targetNameBufferOffset = derBuffer.readInt32LE(16);
const negotiateFlags = derBuffer.readInt32LE(20);
const serverChallenge = derBuffer.slice(24, 32);
const reserved = derBuffer.slice(32, 40);
if (reserved.toString('hex') != '0000000000000000') { console.log('BAD RESERVED'); }
const targetInfoLen = derBuffer.readInt16LE(40);
const targetInfoLenMax = derBuffer.readInt16LE(42);
const targetInfoBufferOffset = derBuffer.readInt32LE(44);
const targetName = derBuffer.slice(targetNameBufferOffset, targetNameBufferOffset + targetNameLen);
const targetInfoBuf = derBuffer.slice(targetInfoBufferOffset, targetInfoBufferOffset + targetInfoLen);
const targetInfo = decodeTargetInfo(derBuffer.slice(targetInfoBufferOffset, targetInfoBufferOffset + targetInfoLen));
const timestamp = targetInfo[7];
//const timestamp = Buffer.from('7b7b3bee9e5ad801', 'hex'); // **************
if (timestamp == null) { console.log('NO TIMESTAMP'); }
const clientChallenge = crypto.randomBytes(8);
//const clientChallenge = Buffer.from('10aac9679ef64e66', 'hex'); // *****************************
const response_key_nt = ntowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain
const response_key_lm = lmowfv2(ntlm.password, ntlm.user, ntlm.domain); // Password, Username, Domain
//console.log("YST: target_name:", targetInfoBuf.toString('hex'));
//console.log("YST: timestamp:", timestamp.toString('hex'));
//console.log('YST: client_challenge:', clientChallenge.toString('hex'));
//console.log("YST: response_key_nt:", response_key_nt.toString('hex'));
//console.log("YST: response_key_lm:", response_key_lm.toString('hex'));
var resp = compute_response_v2(response_key_nt, response_key_lm, serverChallenge, clientChallenge, timestamp, targetInfoBuf);
const nt_challenge_response = resp[0];
const lm_challenge_response = resp[1];
const session_base_key = resp[2];
//console.log('YST: nt_challenge_response:', nt_challenge_response.toString('hex'));
//console.log('YST: lm_challenge_response:', lm_challenge_response.toString('hex'));
//console.log("YST: session_base_key:", session_base_key.toString('hex'));
const key_exchange_key = kx_key_v2(session_base_key, lm_challenge_response, serverChallenge);
const encrypted_random_session_key = rc4k(key_exchange_key, ntlm.exported_session_key);
//console.log("YST: key_exchange_key:", key_exchange_key.toString('hex'));
//console.log("YST: self.exported_session_key:", ntlm.exported_session_key.toString('hex'));
//console.log("YST: encrypted_random_session_key:", encrypted_random_session_key.toString('hex'));
ntlm.is_unicode = ((negotiateFlags & 1) != 0)
//console.log("YST: self.is_unicode: {}", ntlm.is_unicode);
var xdomain = null;
var xuser = null;
if (ntlm.is_unicode) {
xdomain = toUnicode(ntlm.domain);
xuser = toUnicode(ntlm.user);
} else {
xdomain = Buffer.from(ntlm.domain, 'utf8');
xuser = Buffer.from(ntlm.user, 'utf8');
}
//console.log("YST: domain:", xdomain.toString('hex'));
//console.log("YST: user:", xuser.toString('hex'));
const auth_message_compute = authenticate_message(lm_challenge_response, nt_challenge_response, xdomain, xuser, zeroBuffer(0), encrypted_random_session_key, negotiateFlags);
// Write a tmp message to compute MIC and then include it into final message
const tmp_final_auth_message = Buffer.concat([auth_message_compute[0], zeroBuffer(16), auth_message_compute[1]]);
//console.log("YST: tmp_final_auth_message: {}", tmp_final_auth_message.toString('hex'));
const signature = mic(ntlm.exported_session_key, ntlm.negotiate_message, derBuffer, tmp_final_auth_message);
//console.log("YST: signature: {}", signature.toString('hex'));
const r = Buffer.concat([auth_message_compute[0], signature, auth_message_compute[1]]);
//console.log("YST: read_challenge_message2: {}", r.toString('hex'));
return r;
}
function unitTest() {
console.log('--- Starting RDP NLA Unit Tests');
// Test format of the first client message
var r = create_negotiate_message();
console.log(compareArray(bufToArr(r), [78, 84, 76, 77, 83, 83, 80, 0, 1, 0, 0, 0, 53, 130, 8, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) ? "negotiate_message passed." : "negotiate_message failed.");
// Test of MD4 hash function
r = md4(Buffer.from("foo"));
console.log(compareArray(bufToArr(r), [0x0a, 0xc6, 0x70, 0x0c, 0x49, 0x1d, 0x70, 0xfb, 0x86, 0x50, 0x94, 0x0b, 0x1c, 0xa1, 0xe4, 0xb2]) ? "RC4 passed." : "RC4 failed.");
// Test of the unicode function
r = toUnicode("foo");
console.log(compareArray(bufToArr(r), [0x66, 0x00, 0x6f, 0x00, 0x6f, 0x00]) ? "Unicode passed." : "Unicode failed.");
// Test HMAC_MD5 function
r = hmac_md5(Buffer.from("foo"), Buffer.from("bar"));
console.log(compareArray(bufToArr(r), [0x0c, 0x7a, 0x25, 0x02, 0x81, 0x31, 0x5a, 0xb8, 0x63, 0x54, 0x9f, 0x66, 0xcd, 0x8a, 0x3a, 0x53]) ? "HMAC_MD5 passed." : "HMAC_MD5 failed.");
// Test NTOWFv2 function
r = ntowfv2("foo", "user", "domain");
console.log(compareArray(bufToArr(r), [0x6e, 0x53, 0xb9, 0x0, 0x97, 0x8c, 0x87, 0x1f, 0x91, 0xde, 0x6, 0x44, 0x9d, 0x8b, 0x8b, 0x81]) ? "NTOWFv2 passed." : "NTOWFv2 failed.");
// Test LMOWFv2 function
r = ntowfv2("foo", "user", "domain");
console.log(compareArray(bufToArr(r), ntowfv2("foo", "user", "domain")) ? "LMOWFv2 passed." : "LMOWFv2 failed.");
// Test compute response v2 function
r = compute_response_v2(Buffer.from("a"), Buffer.from("b"), Buffer.from("c"), Buffer.from("d"), Buffer.from("e"), Buffer.from("f"));
console.log(compareArray(bufToArr(r[0]), [0xb4, 0x23, 0x84, 0xf, 0x6e, 0x83, 0xc1, 0x5a, 0x45, 0x4f, 0x4c, 0x92, 0x7a, 0xf2, 0xc3, 0x3e, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x65, 0x64, 0x0, 0x0, 0x0, 0x0, 0x66]) ? "responsev2 1 passed." : "responsev2 1 failed.");
console.log(compareArray(bufToArr(r[1]), [0x56, 0xba, 0xff, 0x2d, 0x98, 0xbe, 0xcd, 0xa5, 0x6d, 0xe6, 0x17, 0x89, 0xe1, 0xed, 0xca, 0xae, 0x64]) ? "responsev2 2 passed." : "responsev2 2 failed.");
console.log(compareArray(bufToArr(r[2]), [0x40, 0x3b, 0x33, 0xe5, 0x24, 0x34, 0x3c, 0xc3, 0x24, 0xa0, 0x4d, 0x77, 0x75, 0x34, 0xa4, 0xd0]) ? "responsev2 3 passed." : "responsev2 3 failed.");
// Test of rc4k function
r = rc4k(Buffer.from("foo"), Buffer.from("bar"));
console.log(compareArray(bufToArr(r), [201, 67, 159]) ? "rc4k passed." : "rc4k failed.");
// Test of sign_key function
r = sign_key(Buffer.from("foo"), true);
console.log(compareArray(bufToArr(r), [253, 238, 149, 155, 221, 78, 43, 179, 82, 61, 111, 132, 168, 68, 222, 15]) ? "sign_key 1 passed." : "sign_key 1 failed.");
r = sign_key(Buffer.from("foo"), false);
console.log(compareArray(bufToArr(r), [90, 201, 12, 225, 140, 156, 151, 61, 156, 56, 31, 254, 10, 223, 252, 74]) ? "sign_key 2 passed." : "sign_key 2 failed.");
// Test of seal_key function
r = seal_key(Buffer.from("foo"), true);
console.log(compareArray(bufToArr(r), [20, 213, 185, 176, 168, 142, 134, 244, 36, 249, 89, 247, 180, 36, 162, 101]) ? "seal_key 1 passed." : "seal_key 1 failed.");
r = seal_key(Buffer.from("foo"), false);
console.log(compareArray(bufToArr(r), [64, 125, 160, 17, 144, 165, 62, 226, 22, 125, 128, 31, 103, 141, 55, 40]) ? "seal_key 2 passed." : "seal_key 2 failed.");
// Test signature function
var rc4 = crypto.createCipheriv('rc4', Buffer.from("foo"), null);
r = mac(rc4, Buffer.from("bar"), 0, Buffer.from("data"));
console.log(compareArray(bufToArr(r), [1, 0, 0, 0, 77, 211, 144, 84, 51, 242, 202, 176, 0, 0, 0, 0]) ? "Signature passed." : "Signature failed.");
// Test challenge message
r = authenticate_message(Buffer.from("foo"), Buffer.from("foo"), Buffer.from("domain"), Buffer.from("user"), Buffer.from("workstation"), Buffer.from("foo"), 0);
var buf = Buffer.concat([r[0], Buffer.alloc(16), r[1]]);
console.log(compareArray(bufToArr(buf), [78, 84, 76, 77, 83, 83, 80, 0, 3, 0, 0, 0, 3, 0, 3, 0, 80, 0, 0, 0, 3, 0, 3, 0, 83, 0, 0, 0, 6, 0, 6, 0, 86, 0, 0, 0, 4, 0, 4, 0, 92, 0, 0, 0, 11, 0, 11, 0, 96, 0, 0, 0, 3, 0, 3, 0, 107, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 102, 111, 111, 102, 111, 111, 100, 111, 109, 97, 105, 110, 117, 115, 101, 114, 119, 111, 114, 107, 115, 116, 97, 116, 105, 111, 110, 102, 111, 111]) ? "Challenge message passed." : "Challenge message failed.");
// Test RC4
rc4 = crypto.createCipheriv('rc4', Buffer.from("foo"), null);
r = rc4.update(Buffer.from("bar"));
console.log(compareArray(bufToArr(r), [201, 67, 159]) ? "RC4 1 passed." : "RC4 1 failed.");
r = rc4.update(Buffer.from("bar"));
console.log(compareArray(bufToArr(r), [75, 169, 19]) ? "RC4 2 passed." : "RC4 2 failed.");
// Test create_ts_authenticate
r = create_ts_authenticate(Buffer.from("000102", 'hex'), Buffer.from("000102", 'hex'));
console.log(compareArray(bufToArr(r), [48, 25, 160, 3, 2, 1, 2, 161, 11, 48, 9, 48, 7, 160, 5, 4, 3, 0, 1, 2, 163, 5, 4, 3, 0, 1, 2]) ? "create_ts_authenticate passed." : "create_ts_authenticate failed.");
// Test test_create_ts_credentials
r = create_ts_credentials(Buffer.from("domain"), Buffer.from("user"), Buffer.from("password"));
console.log(compareArray(bufToArr(r), [48, 41, 160, 3, 2, 1, 1, 161, 34, 4, 32, 48, 30, 160, 8, 4, 6, 100, 111, 109, 97, 105, 110, 161, 6, 4, 4, 117, 115, 101, 114, 162, 10, 4, 8, 112, 97, 115, 115, 119, 111, 114, 100]) ? "test_create_ts_credentials passed." : "test_create_ts_credentials failed.");
// Test create_ts_authinfo
r = create_ts_authinfo(Buffer.from("foo"));
console.log(compareArray(bufToArr(r), [48, 12, 160, 3, 2, 1, 2, 162, 5, 4, 3, 102, 111, 111]) ? "create_ts_authinfo passed." : "create_ts_authinfo failed.");
console.log('--- RDP NLA Unit Tests Completed');
}

704
rdp/protocol/pdu/caps.js Normal file
View file

@ -0,0 +1,704 @@
/*
* 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 type = require('../../core').type;
var log = require('../../core').log;
var error = require('../../core').error;
/**
* @see http://msdn.microsoft.com/en-us/library/cc240486.aspx
*/
var CapsType = {
CAPSTYPE_GENERAL : 0x0001,
CAPSTYPE_BITMAP : 0x0002,
CAPSTYPE_ORDER : 0x0003,
CAPSTYPE_BITMAPCACHE : 0x0004,
CAPSTYPE_CONTROL : 0x0005,
CAPSTYPE_ACTIVATION : 0x0007,
CAPSTYPE_POINTER : 0x0008,
CAPSTYPE_SHARE : 0x0009,
CAPSTYPE_COLORCACHE : 0x000A,
CAPSTYPE_SOUND : 0x000C,
CAPSTYPE_INPUT : 0x000D,
CAPSTYPE_FONT : 0x000E,
CAPSTYPE_BRUSH : 0x000F,
CAPSTYPE_GLYPHCACHE : 0x0010,
CAPSTYPE_OFFSCREENCACHE : 0x0011,
CAPSTYPE_BITMAPCACHE_HOSTSUPPORT : 0x0012,
CAPSTYPE_BITMAPCACHE_REV2 : 0x0013,
CAPSTYPE_VIRTUALCHANNEL : 0x0014,
CAPSTYPE_DRAWNINEGRIDCACHE : 0x0015,
CAPSTYPE_DRAWGDIPLUS : 0x0016,
CAPSTYPE_RAIL : 0x0017,
CAPSTYPE_WINDOW : 0x0018,
CAPSETTYPE_COMPDESK : 0x0019,
CAPSETTYPE_MULTIFRAGMENTUPDATE : 0x001A,
CAPSETTYPE_LARGE_POINTER : 0x001B,
CAPSETTYPE_SURFACE_COMMANDS : 0x001C,
CAPSETTYPE_BITMAP_CODECS : 0x001D,
CAPSSETTYPE_FRAME_ACKNOWLEDGE : 0x001E
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240549.aspx
*/
var MajorType = {
OSMAJORTYPE_UNSPECIFIED : 0x0000,
OSMAJORTYPE_WINDOWS : 0x0001,
OSMAJORTYPE_OS2 : 0x0002,
OSMAJORTYPE_MACINTOSH : 0x0003,
OSMAJORTYPE_UNIX : 0x0004,
OSMAJORTYPE_IOS : 0x0005,
OSMAJORTYPE_OSX : 0x0006,
OSMAJORTYPE_ANDROID : 0x0007
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240549.aspx
*/
var MinorType = {
OSMINORTYPE_UNSPECIFIED : 0x0000,
OSMINORTYPE_WINDOWS_31X : 0x0001,
OSMINORTYPE_WINDOWS_95 : 0x0002,
OSMINORTYPE_WINDOWS_NT : 0x0003,
OSMINORTYPE_OS2_V21 : 0x0004,
OSMINORTYPE_POWER_PC : 0x0005,
OSMINORTYPE_MACINTOSH : 0x0006,
OSMINORTYPE_NATIVE_XSERVER : 0x0007,
OSMINORTYPE_PSEUDO_XSERVER : 0x0008,
OSMINORTYPE_WINDOWS_RT : 0x0009
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240549.aspx
*/
var GeneralExtraFlag = {
FASTPATH_OUTPUT_SUPPORTED : 0x0001,
NO_BITMAP_COMPRESSION_HDR : 0x0400,
LONG_CREDENTIALS_SUPPORTED : 0x0004,
AUTORECONNECT_SUPPORTED : 0x0008,
ENC_SALTED_CHECKSUM : 0x0010
};
var Boolean = {
FALSE : 0x00,
TRUE : 0x01
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240556.aspx
*/
var OrderFlag = {
NEGOTIATEORDERSUPPORT : 0x0002,
ZEROBOUNDSDELTASSUPPORT : 0x0008,
COLORINDEXSUPPORT : 0x0020,
SOLIDPATTERNBRUSHONLY : 0x0040,
ORDERFLAGS_EXTRA_FLAGS : 0x0080
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240556.aspx
*/
var Order = {
TS_NEG_DSTBLT_INDEX : 0x00,
TS_NEG_PATBLT_INDEX : 0x01,
TS_NEG_SCRBLT_INDEX : 0x02,
TS_NEG_MEMBLT_INDEX : 0x03,
TS_NEG_MEM3BLT_INDEX : 0x04,
TS_NEG_DRAWNINEGRID_INDEX : 0x07,
TS_NEG_LINETO_INDEX : 0x08,
TS_NEG_MULTI_DRAWNINEGRID_INDEX : 0x09,
TS_NEG_SAVEBITMAP_INDEX : 0x0B,
TS_NEG_MULTIDSTBLT_INDEX : 0x0F,
TS_NEG_MULTIPATBLT_INDEX : 0x10,
TS_NEG_MULTISCRBLT_INDEX : 0x11,
TS_NEG_MULTIOPAQUERECT_INDEX : 0x12,
TS_NEG_FAST_INDEX_INDEX : 0x13,
TS_NEG_POLYGON_SC_INDEX : 0x14,
TS_NEG_POLYGON_CB_INDEX : 0x15,
TS_NEG_POLYLINE_INDEX : 0x16,
TS_NEG_FAST_GLYPH_INDEX : 0x18,
TS_NEG_ELLIPSE_SC_INDEX : 0x19,
TS_NEG_ELLIPSE_CB_INDEX : 0x1A,
TS_NEG_INDEX_INDEX : 0x1B
};
var OrderEx = {
ORDERFLAGS_EX_CACHE_BITMAP_REV3_SUPPORT : 0x0002,
ORDERFLAGS_EX_ALTSEC_FRAME_MARKER_SUPPORT : 0x0004
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240563.aspx
*/
var InputFlags = {
INPUT_FLAG_SCANCODES : 0x0001,
INPUT_FLAG_MOUSEX : 0x0004,
INPUT_FLAG_FASTPATH_INPUT : 0x0008,
INPUT_FLAG_UNICODE : 0x0010,
INPUT_FLAG_FASTPATH_INPUT2 : 0x0020,
INPUT_FLAG_UNUSED1 : 0x0040,
INPUT_FLAG_UNUSED2 : 0x0080,
TS_INPUT_FLAG_MOUSE_HWHEEL : 0x0100
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240564.aspx
*/
var BrushSupport = {
BRUSH_DEFAULT : 0x00000000,
BRUSH_COLOR_8x8 : 0x00000001,
BRUSH_COLOR_FULL : 0x00000002
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240565.aspx
*/
var GlyphSupport = {
GLYPH_SUPPORT_NONE : 0x0000,
GLYPH_SUPPORT_PARTIAL : 0x0001,
GLYPH_SUPPORT_FULL : 0x0002,
GLYPH_SUPPORT_ENCODE : 0x0003
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240550.aspx
*/
var OffscreenSupportLevel = {
FALSE : 0x00000000,
TRUE : 0x00000001
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240551.aspx
*/
var VirtualChannelCompressionFlag = {
VCCAPS_NO_COMPR : 0x00000000,
VCCAPS_COMPR_SC : 0x00000001,
VCCAPS_COMPR_CS_8K : 0x00000002
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240552.aspx
*/
var SoundFlag = {
NONE : 0x0000,
SOUND_BEEPS_FLAG : 0x0001
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240549.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function generalCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_GENERAL,
osMajorType : new type.UInt16Le(),
osMinorType : new type.UInt16Le(),
protocolVersion : new type.UInt16Le(0x0200, {constant : true}),
pad2octetsA : new type.UInt16Le(),
generalCompressionTypes : new type.UInt16Le(0, {constant : true}),
extraFlags : new type.UInt16Le(),
updateCapabilityFlag : new type.UInt16Le(0, {constant : true}),
remoteUnshareFlag : new type.UInt16Le(0, {constant : true}),
generalCompressionLevel : new type.UInt16Le(0, {constant : true}),
refreshRectSupport : new type.UInt8(),
suppressOutputSupport : new type.UInt8()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240554.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function bitmapCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_BITMAP,
preferredBitsPerPixel : new type.UInt16Le(),
receive1BitPerPixel : new type.UInt16Le(0x0001),
receive4BitsPerPixel : new type.UInt16Le(0x0001),
receive8BitsPerPixel : new type.UInt16Le(0x0001),
desktopWidth : new type.UInt16Le(),
desktopHeight : new type.UInt16Le(),
pad2octets : new type.UInt16Le(),
desktopResizeFlag : new type.UInt16Le(),
bitmapCompressionFlag : new type.UInt16Le(0x0001, {constant : true}),
highColorFlags : new type.UInt8(0),
drawingFlags : new type.UInt8(),
multipleRectangleSupport : new type.UInt16Le(0x0001, {constant : true}),
pad2octetsB : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240556.aspx
* @param orders {type.BinaryString|null} list of available orders
* @param opt {object} type options
* @returns {type.Component}
*/
function orderCapability(orders, opt) {
if(orders && orders.size() !== 32) {
throw new error.FatalError('NODE_RDP_PROTOCOL_PDU_CAPS_BAD_ORDERS_SIZE');
}
var self = {
__TYPE__ : CapsType.CAPSTYPE_ORDER,
terminalDescriptor : new type.BinaryString(Buffer.from(Array(16 + 1).join('\x00'), 'binary'), {readLength : new type.CallableValue(16)}),
pad4octetsA : new type.UInt32Le(0),
desktopSaveXGranularity : new type.UInt16Le(1),
desktopSaveYGranularity : new type.UInt16Le(20),
pad2octetsA : new type.UInt16Le(0),
maximumOrderLevel : new type.UInt16Le(1),
numberFonts : new type.UInt16Le(),
orderFlags : new type.UInt16Le(OrderFlag.NEGOTIATEORDERSUPPORT),
orderSupport : orders || new type.Factory(function(s) {
self.orderSupport = new type.BinaryString(null, {readLength : new type.CallableValue(32)}).read(s);
}),
textFlags : new type.UInt16Le(),
orderSupportExFlags : new type.UInt16Le(),
pad4octetsB : new type.UInt32Le(),
desktopSaveSize : new type.UInt32Le(480 * 480),
pad2octetsC : new type.UInt16Le(),
pad2octetsD : new type.UInt16Le(),
textANSICodePage : new type.UInt16Le(0),
pad2octetsE : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240559.aspx
* @param opt type options
* @returns {type.Component}
*/
function bitmapCacheCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_BITMAPCACHE,
pad1 : new type.UInt32Le(),
pad2 : new type.UInt32Le(),
pad3 : new type.UInt32Le(),
pad4 : new type.UInt32Le(),
pad5 : new type.UInt32Le(),
pad6 : new type.UInt32Le(),
cache0Entries : new type.UInt16Le(),
cache0MaximumCellSize : new type.UInt16Le(),
cache1Entries : new type.UInt16Le(),
cache1MaximumCellSize : new type.UInt16Le(),
cache2Entries : new type.UInt16Le(),
cache2MaximumCellSize : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
*
* @param isServer {boolean} true if in server mode
* @param opt {object} type options
* @returns {type.Component}
*/
function pointerCapability(isServer, opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_POINTER,
colorPointerFlag : new type.UInt16Le(),
colorPointerCacheSize : new type.UInt16Le(20),
//old version of rdp doesn't support ...
pointerCacheSize : new type.UInt16Le(null, {conditional : function() {
return isServer || false;
}})
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240563.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function inputCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_INPUT,
inputFlags : new type.UInt16Le(),
pad2octetsA : new type.UInt16Le(),
// same value as gcc.ClientCoreSettings.kbdLayout
keyboardLayout : new type.UInt32Le(),
// same value as gcc.ClientCoreSettings.keyboardType
keyboardType : new type.UInt32Le(),
// same value as gcc.ClientCoreSettings.keyboardSubType
keyboardSubType : new type.UInt32Le(),
// same value as gcc.ClientCoreSettings.keyboardFnKeys
keyboardFunctionKey : new type.UInt32Le(),
// same value as gcc.ClientCoreSettingrrs.imeFileName
imeFileName : new type.BinaryString(Buffer.from(Array(64 + 1).join('\x00'), 'binary'), {readLength : new type.CallableValue(64)})
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240564.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function brushCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_BRUSH,
brushSupportLevel : new type.UInt32Le(BrushSupport.BRUSH_DEFAULT)
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240566.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function cacheEntry(opt) {
var self = {
cacheEntries : new type.UInt16Le(),
cacheMaximumCellSize : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240565.aspx
* @param entries {type.Component} cache entries
* @param opt {object} type options
* @returns {type.Component}
*/
function glyphCapability(entries, opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_GLYPHCACHE,
glyphCache : entries || new type.Factory(function(s) {
self.glyphCache = new type.Component([]);
for(var i = 0; i < 10; i++) {
self.glyphCache.obj.push(cacheEntry().read(s));
}
}),
fragCache : new type.UInt32Le(),
// all fonts are sent with bitmap format (very expensive)
glyphSupportLevel : new type.UInt16Le(GlyphSupport.GLYPH_SUPPORT_NONE),
pad2octets : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240550.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function offscreenBitmapCacheCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_OFFSCREENCACHE,
offscreenSupportLevel : new type.UInt32Le(OffscreenSupportLevel.FALSE),
offscreenCacheSize : new type.UInt16Le(),
offscreenCacheEntries : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240551.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function virtualChannelCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_VIRTUALCHANNEL,
flags : new type.UInt32Le(VirtualChannelCompressionFlag.VCCAPS_NO_COMPR),
VCChunkSize : new type.UInt32Le(null, {optional : true})
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240552.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function soundCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_SOUND,
soundFlags : new type.UInt16Le(SoundFlag.NONE),
pad2octetsA : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240568.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function controlCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_CONTROL,
controlFlags : new type.UInt16Le(),
remoteDetachFlag : new type.UInt16Le(),
controlInterest : new type.UInt16Le(0x0002),
detachInterest : new type.UInt16Le(0x0002)
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240569.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function windowActivationCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_ACTIVATION,
helpKeyFlag : new type.UInt16Le(),
helpKeyIndexFlag : new type.UInt16Le(),
helpExtendedKeyFlag : new type.UInt16Le(),
windowManagerKeyFlag : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240571.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function fontCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_FONT,
fontSupportFlags : new type.UInt16Le(0x0001),
pad2octets : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241564.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function colorCacheCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_COLORCACHE,
colorTableCacheSize : new type.UInt16Le(0x0006),
pad2octets : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240570.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function shareCapability(opt) {
var self = {
__TYPE__ : CapsType.CAPSTYPE_SHARE,
nodeId : new type.UInt16Le(),
pad2octets : new type.UInt16Le()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240649.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function multiFragmentUpdate(opt) {
var self = {
__TYPE__ : CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE,
MaxRequestSize : new type.UInt32Le(0)
};
return new type.Component(self, opt);
}
/**
* Capability wrapper packet
* @see http://msdn.microsoft.com/en-us/library/cc240486.aspx
* @param cap {type.Component}
* @param opt {object} type options
* @returns {type.Component}
*/
function capability(cap, opt) {
var self = {
capabilitySetType : new type.UInt16Le(function() {
return self.capability.obj.__TYPE__;
}),
lengthCapability : new type.UInt16Le(function() {
return new type.Component(self).size();
}),
capability : cap || new type.Factory(function(s) {
switch(self.capabilitySetType.value) {
case CapsType.CAPSTYPE_GENERAL:
self.capability = generalCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_BITMAP:
self.capability = bitmapCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_ORDER:
self.capability = orderCapability(null, {readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_BITMAPCACHE:
self.capability = bitmapCacheCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_POINTER:
self.capability = pointerCapability(false, {readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_INPUT:
self.capability = inputCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_BRUSH:
self.capability = brushCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_GLYPHCACHE:
self.capability = glyphCapability(null, {readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_OFFSCREENCACHE:
self.capability = offscreenBitmapCacheCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_VIRTUALCHANNEL:
self.capability = virtualChannelCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_SOUND:
self.capability = soundCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_CONTROL:
self.capability = controlCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_ACTIVATION:
self.capability = windowActivationCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_FONT:
self.capability = fontCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_COLORCACHE:
self.capability = colorCacheCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSTYPE_SHARE:
self.capability = shareCapability({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
case CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE:
self.capability = multiFragmentUpdate({readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
break;
default:
log.debug('unknown capability ' + self.capabilitySetType.value);
self.capability = new type.BinaryString(null, {readLength : new type.CallableValue(function() {
return self.lengthCapability.value - 4;
})}).read(s);
}
})
};
return new type.Component(self, opt);
}
/**
* Module exports
*/
module.exports = {
CapsType : CapsType,
MajorType : MajorType,
MinorType : MinorType,
GeneralExtraFlag : GeneralExtraFlag,
Boolean : Boolean,
OrderFlag : OrderFlag,
Order : Order,
OrderEx : OrderEx,
InputFlags : InputFlags,
BrushSupport : BrushSupport,
GlyphSupport : GlyphSupport,
OffscreenSupportLevel : OffscreenSupportLevel,
VirtualChannelCompressionFlag : VirtualChannelCompressionFlag,
SoundFlag : SoundFlag,
generalCapability : generalCapability,
bitmapCapability : bitmapCapability,
orderCapability : orderCapability,
bitmapCacheCapability : bitmapCacheCapability,
pointerCapability : pointerCapability,
inputCapability : inputCapability,
brushCapability : brushCapability,
cacheEntry : cacheEntry,
glyphCapability : glyphCapability,
offscreenBitmapCacheCapability : offscreenBitmapCacheCapability,
virtualChannelCapability : virtualChannelCapability,
soundCapability : soundCapability,
controlCapability : controlCapability,
windowActivationCapability : windowActivationCapability,
fontCapability : fontCapability,
colorCacheCapability : colorCacheCapability,
shareCapability : shareCapability,
multiFragmentUpdate : multiFragmentUpdate,
capability : capability
};

1151
rdp/protocol/pdu/data.js Normal file

File diff suppressed because it is too large Load diff

402
rdp/protocol/pdu/global.js Normal file
View file

@ -0,0 +1,402 @@
/*
* 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 events = require('events');
var caps = require('./caps');
var data = require('./data');
var type = require('../../core').type;
var log = require('../../core').log;
/**
* Global channel for all graphic updates
* capabilities exchange and input handles
*/
function Global(transport, fastPathTransport) {
this.transport = transport;
this.fastPathTransport = fastPathTransport;
// must be init via connect event
this.userId = 0;
this.serverCapabilities = [];
this.clientCapabilities = [];
}
//inherit from Layer
inherits(Global, events.EventEmitter);
/**
* Send formated PDU message
* @param message {type.Component} PDU message
*/
Global.prototype.sendPDU = function(message) {
this.transport.send(data.pdu(this.userId, message));
};
/**
* Send formated Data PDU message
* @param message {type.Component} PDU message
*/
Global.prototype.sendDataPDU = function(message) {
this.sendPDU(data.dataPDU(message, this.shareId));
};
/**
* Client side of Global channel automata
* @param transport
*/
function Client(transport, fastPathTransport) {
Global.call(this, transport, fastPathTransport);
var self = this;
this.transport.once('connect', function(core, userId, channelId) {
self.connect(core, userId, channelId);
}).on('close', function() {
self.emit('close');
}).on('error', function (err) {
self.emit('error', err);
});
if (this.fastPathTransport) {
this.fastPathTransport.on('fastPathData', function (secFlag, s) {
self.recvFastPath(secFlag, s);
});
}
// init client capabilities
this.clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL] = caps.generalCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP] = caps.bitmapCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_ORDER] = caps.orderCapability(
new type.Component([
new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0),
new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0),
new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0),
new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0), new type.UInt8(0)
]));
this.clientCapabilities[caps.CapsType.CAPSTYPE_BITMAPCACHE] = caps.bitmapCacheCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_POINTER] = caps.pointerCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_INPUT] = caps.inputCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_BRUSH] = caps.brushCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_GLYPHCACHE] = caps.glyphCapability(
new type.Component([
caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry(),
caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry(), caps.cacheEntry()
]));
this.clientCapabilities[caps.CapsType.CAPSTYPE_OFFSCREENCACHE] = caps.offscreenBitmapCacheCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_VIRTUALCHANNEL] = caps.virtualChannelCapability();
this.clientCapabilities[caps.CapsType.CAPSTYPE_SOUND] = caps.soundCapability();
this.clientCapabilities[caps.CapsType.CAPSETTYPE_MULTIFRAGMENTUPDATE] = caps.multiFragmentUpdate();
}
// inherit from Layer
inherits(Client, Global);
/**
* connect function
* @param gccCore {type.Component(clientCoreData)}
*/
Client.prototype.connect = function(gccCore, userId, channelId) {
this.gccCore = gccCore;
this.userId = userId;
this.channelId = channelId;
var self = this;
this.transport.once('data', function(s) {
self.recvDemandActivePDU(s);
});
};
/**
* close stack
*/
Client.prototype.close = function() {
this.transport.close();
};
/**
* Receive capabilities from server
* @param s {type.Stream}
*/
Client.prototype.recvDemandActivePDU = function(s) {
var pdu = data.pdu().read(s);
if (pdu.obj.shareControlHeader.obj.pduType.value !== data.PDUType.PDUTYPE_DEMANDACTIVEPDU) {
log.debug('ignore message type ' + pdu.obj.shareControlHeader.obj.pduType.value + ' during connection sequence');
// loop on state
var self = this;
this.transport.once('data', function(s) {
self.recvDemandActivePDU(s);
});
return;
}
// store share id
this.shareId = pdu.obj.pduMessage.obj.shareId.value;
// store server capabilities
for(var i in pdu.obj.pduMessage.obj.capabilitySets.obj) {
var cap = pdu.obj.pduMessage.obj.capabilitySets.obj[i].obj.capability;
if(!cap.obj) {
continue;
}
this.serverCapabilities[cap.obj.__TYPE__] = cap;
}
this.transport.enableSecureCheckSum = !!(this.serverCapabilities[caps.CapsType.CAPSTYPE_GENERAL].obj.extraFlags.value & caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM);
this.sendConfirmActivePDU();
this.sendClientFinalizeSynchronizePDU();
var self = this;
this.transport.once('data', function(s) {
self.recvServerSynchronizePDU(s);
});
};
/**
* global channel automata state
* @param s {type.Stream}
*/
Client.prototype.recvServerSynchronizePDU = function(s) {
var pdu = data.pdu().read(s);
if ( pdu.obj.shareControlHeader.obj.pduType.value !== data.PDUType.PDUTYPE_DATAPDU
|| pdu.obj.pduMessage.obj.shareDataHeader.obj.pduType2.value !== data.PDUType2.PDUTYPE2_SYNCHRONIZE) {
log.debug('ignore message type ' + pdu.obj.shareControlHeader.obj.pduType.value + ' during connection sequence');
// loop on state
var self = this;
this.transport.once('data', function(s) {
self.recvServerSynchronizePDU(s);
});
return;
}
var self = this;
this.transport.once('data', function(s) {
self.recvServerControlCooperatePDU(s);
});
};
/**
* global channel automata state
* @param s {type.Stream}
*/
Client.prototype.recvServerControlCooperatePDU = function(s) {
var pdu = data.pdu().read(s);
if ( pdu.obj.shareControlHeader.obj.pduType.value !== data.PDUType.PDUTYPE_DATAPDU
|| pdu.obj.pduMessage.obj.shareDataHeader.obj.pduType2.value !== data.PDUType2.PDUTYPE2_CONTROL
|| pdu.obj.pduMessage.obj.pduData.obj.action.value !== data.Action.CTRLACTION_COOPERATE) {
log.debug('ignore message type ' + pdu.obj.shareControlHeader.obj.pduType.value + ' during connection sequence');
// loop on state
var self = this;
this.transport.once('data', function(s) {
self.recvServerControlCooperatePDU(s);
});
}
var self = this;
this.transport.once('data', function(s) {
self.recvServerControlGrantedPDU(s);
});
};
/**
* global channel automata state
* @param s {type.Stream}
*/
Client.prototype.recvServerControlGrantedPDU = function(s) {
var pdu = data.pdu().read(s);
if ( pdu.obj.shareControlHeader.obj.pduType.value !== data.PDUType.PDUTYPE_DATAPDU
|| pdu.obj.pduMessage.obj.shareDataHeader.obj.pduType2.value !== data.PDUType2.PDUTYPE2_CONTROL
|| pdu.obj.pduMessage.obj.pduData.obj.action.value !== data.Action.CTRLACTION_GRANTED_CONTROL) {
log.debug('ignore message type ' + pdu.obj.shareControlHeader.obj.pduType.value + ' during connection sequence');
// loop on state
var self = this;
this.transport.once('data', function(s) {
self.recvServerControlGrantedPDU(s);
});
}
var self = this;
this.transport.once('data', function(s) {
self.recvServerFontMapPDU(s);
});
};
/**
* global channel automata state
* @param s {type.Stream}
*/
Client.prototype.recvServerFontMapPDU = function(s) {
var pdu = data.pdu().read(s);
if ( pdu.obj.shareControlHeader.obj.pduType.value !== data.PDUType.PDUTYPE_DATAPDU
|| pdu.obj.pduMessage.obj.shareDataHeader.obj.pduType2.value !== data.PDUType2.PDUTYPE2_FONTMAP) {
log.debug('ignore message type ' + pdu.obj.shareControlHeader.obj.pduType.value + ' during connection sequence');
// loop on state
var self = this;
this.transport.once('data', function(s) {
self.recvServerFontMapPDU(s);
});
}
this.emit('connect');
var self = this;
this.transport.on('data', function(s) {
self.recvPDU(s);
});
};
/**
* Main reveive fast path
* @param secFlag {integer}
* @param s {type.Stream}
*/
Client.prototype.recvFastPath = function (secFlag, s) {
while (s.availableLength() > 0) {
var pdu = data.fastPathUpdatePDU().read(s);
switch (pdu.obj.updateHeader.value & 0xf) {
case data.FastPathUpdateType.FASTPATH_UPDATETYPE_BITMAP:
this.emit('bitmap', pdu.obj.updateData.obj.rectangles.obj);
break;
default:
}
}
};
/**
* global channel automata state
* @param s {type.Stream}
*/
Client.prototype.recvPDU = function(s) {
while (s.availableLength() > 0) {
var pdu = data.pdu().read(s);
switch(pdu.obj.shareControlHeader.obj.pduType.value) {
case data.PDUType.PDUTYPE_DEACTIVATEALLPDU:
var self = this;
this.transport.removeAllListeners('data');
this.transport.once('data', function(s) {
self.recvDemandActivePDU(s);
});
break;
case data.PDUType.PDUTYPE_DATAPDU:
this.readDataPDU(pdu.obj.pduMessage)
break;
default:
log.debug('ignore pdu type ' + pdu.obj.shareControlHeader.obj.pduType.value);
}
}
};
/**
* main receive for data PDU packet
* @param dataPDU {data.dataPDU}
*/
Client.prototype.readDataPDU = function (dataPDU) {
switch(dataPDU.obj.shareDataHeader.obj.pduType2.value) {
case data.PDUType2.PDUTYPE2_SET_ERROR_INFO_PDU:
break;
case data.PDUType2.PDUTYPE2_SHUTDOWN_DENIED:
this.transport.close();
break;
case data.PDUType2.PDUTYPE2_SAVE_SESSION_INFO:
this.emit('session');
break;
case data.PDUType2.PDUTYPE2_UPDATE:
this.readUpdateDataPDU(dataPDU.obj.pduData)
break;
}
};
/**
* Main upadate pdu receive function
* @param updateDataPDU
*/
Client.prototype.readUpdateDataPDU = function (updateDataPDU) {
switch(updateDataPDU.obj.updateType.value) {
case data.UpdateType.UPDATETYPE_BITMAP:
this.emit('bitmap', updateDataPDU.obj.updateData.obj.rectangles.obj)
break;
}
};
/**
* send all client capabilities
*/
Client.prototype.sendConfirmActivePDU = function () {
var generalCapability = this.clientCapabilities[caps.CapsType.CAPSTYPE_GENERAL].obj;
generalCapability.osMajorType.value = caps.MajorType.OSMAJORTYPE_WINDOWS;
generalCapability.osMinorType.value = caps.MinorType.OSMINORTYPE_WINDOWS_NT;
generalCapability.extraFlags.value = caps.GeneralExtraFlag.LONG_CREDENTIALS_SUPPORTED
| caps.GeneralExtraFlag.NO_BITMAP_COMPRESSION_HDR
| caps.GeneralExtraFlag.ENC_SALTED_CHECKSUM
| caps.GeneralExtraFlag.FASTPATH_OUTPUT_SUPPORTED;
var bitmapCapability = this.clientCapabilities[caps.CapsType.CAPSTYPE_BITMAP].obj;
bitmapCapability.preferredBitsPerPixel.value = this.gccCore.highColorDepth.value;
bitmapCapability.desktopWidth.value = this.gccCore.desktopWidth.value;
bitmapCapability.desktopHeight.value = this.gccCore.desktopHeight.value;
var orderCapability = this.clientCapabilities[caps.CapsType.CAPSTYPE_ORDER].obj;
orderCapability.orderFlags.value |= caps.OrderFlag.ZEROBOUNDSDELTASSUPPORT;
var inputCapability = this.clientCapabilities[caps.CapsType.CAPSTYPE_INPUT].obj;
inputCapability.inputFlags.value = caps.InputFlags.INPUT_FLAG_SCANCODES | caps.InputFlags.INPUT_FLAG_MOUSEX | caps.InputFlags.INPUT_FLAG_UNICODE;
inputCapability.keyboardLayout = this.gccCore.kbdLayout;
inputCapability.keyboardType = this.gccCore.keyboardType;
inputCapability.keyboardSubType = this.gccCore.keyboardSubType;
inputCapability.keyboardrFunctionKey = this.gccCore.keyboardFnKeys;
inputCapability.imeFileName = this.gccCore.imeFileName;
var capabilities = new type.Component([]);
for(var i in this.clientCapabilities) {
capabilities.obj.push(caps.capability(this.clientCapabilities[i]));
}
var confirmActivePDU = data.confirmActivePDU(capabilities, this.shareId);
this.sendPDU(confirmActivePDU);
};
/**
* send synchronize PDU
*/
Client.prototype.sendClientFinalizeSynchronizePDU = function() {
this.sendDataPDU(data.synchronizeDataPDU(this.channelId));
this.sendDataPDU(data.controlDataPDU(data.Action.CTRLACTION_COOPERATE));
this.sendDataPDU(data.controlDataPDU(data.Action.CTRLACTION_REQUEST_CONTROL));
this.sendDataPDU(data.fontListDataPDU());
};
/**
* Send input event as slow path input
* @param inputEvents {array}
*/
Client.prototype.sendInputEvents = function (inputEvents) {
var pdu = data.clientInputEventPDU(new type.Component(inputEvents.map(function (e) {
return data.slowPathInputEvent(e);
})));
this.sendDataPDU(pdu);
};
/**
* Module exports
*/
module.exports = {
Client : Client
};

30
rdp/protocol/pdu/index.js Normal file
View file

@ -0,0 +1,30 @@
/*
* 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 lic = require('./lic');
var sec = require('./sec');
var global = require('./global');
var data = require('./data');
module.exports = {
lic : lic,
sec : sec,
global : global,
data : data
};

309
rdp/protocol/pdu/lic.js Normal file
View file

@ -0,0 +1,309 @@
/*
* 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 type = require('../../core').type;
var MessageType = {
LICENSE_REQUEST : 0x01,
PLATFORM_CHALLENGE : 0x02,
NEW_LICENSE : 0x03,
UPGRADE_LICENSE : 0x04,
LICENSE_INFO : 0x12,
NEW_LICENSE_REQUEST : 0x13,
PLATFORM_CHALLENGE_RESPONSE : 0x15,
ERROR_ALERT : 0xFF
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240482.aspx
*/
var ErrorCode = {
ERR_INVALID_SERVER_CERTIFICATE : 0x00000001,
ERR_NO_LICENSE : 0x00000002,
ERR_INVALID_SCOPE : 0x00000004,
ERR_NO_LICENSE_SERVER : 0x00000006,
STATUS_VALID_CLIENT : 0x00000007,
ERR_INVALID_CLIENT : 0x00000008,
ERR_INVALID_PRODUCTID : 0x0000000B,
ERR_INVALID_MESSAGE_LEN : 0x0000000C,
ERR_INVALID_MAC : 0x00000003
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240482.aspx
*/
var StateTransition = {
ST_TOTAL_ABORT : 0x00000001,
ST_NO_TRANSITION : 0x00000002,
ST_RESET_PHASE_TO_START : 0x00000003,
ST_RESEND_LAST_MESSAGE : 0x00000004
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240481.aspx
*/
var BinaryBlobType = {
BB_ANY_BLOB : 0x0000,
BB_DATA_BLOB : 0x0001,
BB_RANDOM_BLOB : 0x0002,
BB_CERTIFICATE_BLOB : 0x0003,
BB_ERROR_BLOB : 0x0004,
BB_ENCRYPTED_DATA_BLOB : 0x0009,
BB_KEY_EXCHG_ALG_BLOB : 0x000D,
BB_SCOPE_BLOB : 0x000E,
BB_CLIENT_USER_NAME_BLOB : 0x000F,
BB_CLIENT_MACHINE_NAME_BLOB : 0x0010
};
var Preambule = {
PREAMBLE_VERSION_2_0 : 0x2,
PREAMBLE_VERSION_3_0 : 0x3,
EXTENDED_ERROR_MSG_SUPPORTED : 0x80
};
/**
* Binary blob to emcompass license information
* @see http://msdn.microsoft.com/en-us/library/cc240481.aspx
* @param blobType {BinaryBlobType.*}
* @returns {type.Component}
*/
function licenseBinaryBlob(blobType) {
blobType = blobType || BinaryBlobType.BB_ANY_BLOB;
var self = {
wBlobType : new type.UInt16Le(blobType, { constant : (blobType === BinaryBlobType.BB_ANY_BLOB)?false:true }),
wBlobLen : new type.UInt16Le(function() {
return self.blobData.size();
}),
blobData : new type.BinaryString(null, { readLength : new type.CallableValue(function() {
return self.wBlobLen.value;
})})
};
return new type.Component(self);
}
/**
* Error message in license PDU automata
* @see http://msdn.microsoft.com/en-us/library/cc240482.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function licensingErrorMessage(opt) {
var self = {
__TYPE__ : MessageType.ERROR_ALERT,
dwErrorCode : new type.UInt32Le(),
dwStateTransition : new type.UInt32Le(),
blob : licenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB)
};
return new type.Component(self, opt);
}
/**
* License product informations
* @see http://msdn.microsoft.com/en-us/library/cc241915.aspx
* @returns {type.Component}
*/
function productInformation() {
var self = {
dwVersion : new type.UInt32Le(),
cbCompanyName : new type.UInt32Le(function() {
return self.pbCompanyName.size();
}),
// may contain "Microsoft Corporation" from server microsoft
pbCompanyName : new type.BinaryString(Buffer.from('Microsoft Corporation', 'ucs2'), { readLength : new type.CallableValue(function() {
return self.cbCompanyName.value;
})}),
cbProductId : new type.UInt32Le(function() {
return self.pbProductId.size();
}),
// may contain "A02" from microsoft license server
pbProductId : new type.BinaryString(Buffer.from('A02', 'ucs2'), { readLength : new type.CallableValue(function() {
return self.cbProductId.value;
})})
};
return new type.Component(self);
}
/**
* Use in license negotiation
* @see http://msdn.microsoft.com/en-us/library/cc241917.aspx
* @returns {type.Component}
*/
function scope() {
var self = {
scope : licenseBinaryBlob(BinaryBlobType.BB_SCOPE_BLOB)
};
return new type.Component(self);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241916.aspx
* @returns {type.Component}
*/
function scopeList() {
var self = {
scopeCount : new type.UInt32Le(function() {
return self.scopeArray.length;
}),
scopeArray : new type.Factory(function(s) {
self.scopeArray = new type.Component([]);
for(var i = 0; i < self.scopeCount.value; i++) {
self.scopeArray.obj.push(scope().read(s));
}
})
};
return new type.Component(self);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241914.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function serverLicenseRequest(opt) {
var self = {
__TYPE__ : MessageType.LICENSE_REQUEST,
serverRandom : new type.BinaryString(Buffer.from(Array(32 + 1).join('\x00')), { readLength : new type.CallableValue(32) } ),
productInfo : productInformation(),
keyExchangeList : licenseBinaryBlob(BinaryBlobType.BB_KEY_EXCHG_ALG_BLOB),
serverCertificate : licenseBinaryBlob(BinaryBlobType.BB_CERTIFICATE_BLOB),
scopeList : scopeList()
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241918.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function clientNewLicenseRequest(opt) {
var self = {
__TYPE__ : MessageType.NEW_LICENSE_REQUEST,
preferredKeyExchangeAlg : new type.UInt32Le(0x00000001, { constant : true }),
// pure microsoft client ;-)
// http://msdn.microsoft.com/en-us/library/1040af38-c733-4fb3-acd1-8db8cc979eda#id10
platformId : new type.UInt32Le(0x04000000 | 0x00010000),
clientRandom : new type.BinaryString(Buffer.from(Array(32 + 1).join('\x00')), { readLength : new type.CallableValue(32) }),
encryptedPreMasterSecret : licenseBinaryBlob(BinaryBlobType.BB_RANDOM_BLOB),
ClientUserName : licenseBinaryBlob(BinaryBlobType.BB_CLIENT_USER_NAME_BLOB),
ClientMachineName : licenseBinaryBlob(BinaryBlobType.BB_CLIENT_MACHINE_NAME_BLOB)
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241921.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function serverPlatformChallenge(opt) {
var self = {
__TYPE__ : MessageType.PLATFORM_CHALLENGE,
connectFlags : new type.UInt32Le(),
encryptedPlatformChallenge : licenseBinaryBlob(BinaryBlobType.BB_ANY_BLOB),
MACData : new type.BinaryString(Buffer.from(Array(16 + 1).join('\x00')), { readLength : new type.CallableValue(16) })
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc241922.aspx
* @param opt {object} type options
* @returns {type.Component}
*/
function clientPLatformChallengeResponse(opt) {
var self = {
__TYPE__ : MessageType.PLATFORM_CHALLENGE_RESPONSE,
encryptedPlatformChallengeResponse : licenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB),
encryptedHWID : licenseBinaryBlob(BinaryBlobType.BB_DATA_BLOB),
MACData : new type.BinaryString(Buffer.from(Array(16 + 1).join('\x00'), 'binary'), { readLength : new type.CallableValue(16) })
};
return new type.Component(self, opt);
};
/**
* Global license packet
* @param packet {type.* | null} send packet
* @returns {type.Component}
*/
function licensePacket(message) {
var self = {
bMsgtype : new type.UInt8(function() {
return self.licensingMessage.obj.__TYPE__;
}),
flag : new type.UInt8(Preambule.PREAMBLE_VERSION_3_0),
wMsgSize : new type.UInt16Le(function() {
return new type.Component(self).size();
}),
licensingMessage : message || new type.Factory(function(s) {
switch(self.bMsgtype.value) {
case MessageType.ERROR_ALERT:
self.licensingMessage = licensingErrorMessage({ readLength : new type.CallableValue(function() {
return self.wMsgSize.value - 4;
})}).read(s);
break;
case MessageType.LICENSE_REQUEST:
self.licensingMessage = serverLicenseRequest({ readLength : new type.CallableValue(function() {
return self.wMsgSize.value - 4;
})}).read(s);
break;
case MessageType.NEW_LICENSE_REQUEST:
self.licensingMessage = clientNewLicenseRequest({ readLength : new type.CallableValue(function() {
return self.wMsgSize.value - 4;
})}).read(s);
break;
case MessageType.PLATFORM_CHALLENGE:
self.licensingMessage = serverPlatformChallenge({ readLength : new type.CallableValue(function() {
return self.wMsgSize.value - 4;
})}).read(s);
break;
case MessageType.PLATFORM_CHALLENGE_RESPONSE:
self.licensingMessage = clientPLatformChallengeResponse({ readLength : new type.CallableValue(function() {
return self.wMsgSize.value - 4;
})}).read(s);
break;
default:
log.error('unknown license message type ' + self.bMsgtype.value);
}
})
};
return new type.Component(self);
}
/**
* Module exports
*/
module.exports = {
MessageType : MessageType,
ErrorCode : ErrorCode,
StateTransition : StateTransition,
licensePacket : licensePacket,
clientNewLicenseRequest : clientNewLicenseRequest,
clientPLatformChallengeResponse : clientPLatformChallengeResponse
};

519
rdp/protocol/pdu/sec.js Normal file
View file

@ -0,0 +1,519 @@
/*
* 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
};

361
rdp/protocol/rdp.js Normal file
View file

@ -0,0 +1,361 @@
/*
* 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 net = require('net');
var inherits = require('util').inherits;
var events = require('events');
var layer = require('../core').layer;
var error = require('../core').error;
var rle = require('../core').rle;
var log = require('../core').log;
var TPKT = require('./tpkt');
var x224 = require('./x224');
var t125 = require('./t125');
var pdu = require('./pdu');
/**
* decompress bitmap from RLE algorithm
* @param bitmap {object} bitmap object of bitmap event of node-rdpjs
*/
function decompress (bitmap) {
var fName = null;
switch (bitmap.bitsPerPixel.value) {
case 15:
fName = 'bitmap_decompress_15';
break;
case 16:
fName = 'bitmap_decompress_16';
break;
case 24:
fName = 'bitmap_decompress_24';
break;
case 32:
fName = 'bitmap_decompress_32';
break;
default:
throw 'invalid bitmap data format';
}
var input = new Uint8Array(bitmap.bitmapDataStream.value);
var inputPtr = rle._malloc(input.length);
var inputHeap = new Uint8Array(rle.HEAPU8.buffer, inputPtr, input.length);
inputHeap.set(input);
var ouputSize = bitmap.width.value * bitmap.height.value * 4;
var outputPtr = rle._malloc(ouputSize);
var outputHeap = new Uint8Array(rle.HEAPU8.buffer, outputPtr, ouputSize);
var res = rle.ccall(fName,
'number',
['number', 'number', 'number', 'number', 'number', 'number', 'number', 'number'],
[outputHeap.byteOffset, bitmap.width.value, bitmap.height.value, bitmap.width.value, bitmap.height.value, inputHeap.byteOffset, input.length]
);
var output = new Uint8ClampedArray(outputHeap.buffer, outputHeap.byteOffset, ouputSize);
rle._free(inputPtr);
rle._free(outputPtr);
return output;
}
/**
* Main RDP module
*/
function RdpClient(config) {
config = config || {};
this.connected = false;
this.bufferLayer = new layer.BufferLayer(new net.Socket());
this.tpkt = new TPKT(this.bufferLayer);
this.x224 = new x224.Client(this.tpkt, config);
this.mcs = new t125.mcs.Client(this.x224);
this.sec = new pdu.sec.Client(this.mcs, this.tpkt);
this.global = new pdu.global.Client(this.sec, this.sec);
// config log level
log.level = log.Levels[config.logLevel || 'INFO'] || log.Levels.INFO;
// credentials
if (config.domain) {
this.sec.infos.obj.domain.value = Buffer.from(config.domain + '\x00', 'ucs2');
}
if (config.userName) {
this.sec.infos.obj.userName.value = Buffer.from(config.userName + '\x00', 'ucs2');
}
if (config.password) {
this.sec.infos.obj.password.value = Buffer.from(config.password + '\x00', 'ucs2');
}
if(config.workingDir) {
this.sec.infos.obj.workingDir.value = Buffer.from(config.workingDir + '\x00', 'ucs2');
}
if(config.alternateShell) {
this.sec.infos.obj.alternateShell.value = Buffer.from(config.alternateShell + '\x00', 'ucs2');
}
if (config.enablePerf) {
this.sec.infos.obj.extendedInfo.obj.performanceFlags.value =
pdu.sec.PerfFlag.PERF_DISABLE_WALLPAPER
| pdu.sec.PerfFlag.PERF_DISABLE_MENUANIMATIONS
| pdu.sec.PerfFlag.PERF_DISABLE_CURSOR_SHADOW
| pdu.sec.PerfFlag.PERF_DISABLE_THEMING
| pdu.sec.PerfFlag.PERF_DISABLE_FULLWINDOWDRAG;
}
if (config.autoLogin) {
this.sec.infos.obj.flag.value |= pdu.sec.InfoFlag.INFO_AUTOLOGON;
}
if (config.screen && config.screen.width && config.screen.height) {
this.mcs.clientCoreData.obj.desktopWidth.value = config.screen.width;
this.mcs.clientCoreData.obj.desktopHeight.value = config.screen.height;
}
log.debug('screen ' + this.mcs.clientCoreData.obj.desktopWidth.value + 'x' + this.mcs.clientCoreData.obj.desktopHeight.value);
// config keyboard layout
switch (config.locale) {
case 'fr':
log.debug('french keyboard layout');
this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.FRENCH;
break;
case 'en':
default:
log.debug('english keyboard layout');
this.mcs.clientCoreData.obj.kbdLayout.value = t125.gcc.KeyboardLayout.US;
}
//bind all events
var self = this;
this.global.on('connect', function () {
self.connected = true;
self.emit('connect');
}).on('session', function () {
self.emit('session');
}).on('close', function () {
self.connected = false;
self.emit('close');
}).on('bitmap', function (bitmaps) {
for(var bitmap in bitmaps) {
var bitmapData = bitmaps[bitmap].obj.bitmapDataStream.value;
var isCompress = bitmaps[bitmap].obj.flags.value & pdu.data.BitmapFlag.BITMAP_COMPRESSION;
if (isCompress && config.decompress) {
bitmapData = decompress(bitmaps[bitmap].obj);
isCompress = false;
}
self.emit('bitmap', {
destTop : bitmaps[bitmap].obj.destTop.value,
destLeft : bitmaps[bitmap].obj.destLeft.value,
destBottom : bitmaps[bitmap].obj.destBottom.value,
destRight : bitmaps[bitmap].obj.destRight.value,
width : bitmaps[bitmap].obj.width.value,
height : bitmaps[bitmap].obj.height.value,
bitsPerPixel : bitmaps[bitmap].obj.bitsPerPixel.value,
isCompress : isCompress,
data : bitmapData
});
}
}).on('error', function (err) {
log.warn(err.code + '(' + err.message + ')\n' + err.stack);
if (err instanceof error.FatalError) {
throw err;
}
else {
self.emit('error', err);
}
});
}
inherits(RdpClient, events.EventEmitter);
/**
* Connect RDP client
* @param host {string} destination host
* @param port {integer} destination port
*/
RdpClient.prototype.connect = function (host, port) {
log.debug('connect to ' + host + ':' + port);
var self = this;
this.bufferLayer.socket.connect(port, host, function () {
// in client mode connection start from x224 layer
self.x224.connect();
});
return this;
};
/**
* Close RDP client
*/
RdpClient.prototype.close = function () {
if(this.connected) {
this.global.close();
}
this.connected = false;
return this;
};
/**
* Send pointer event to server
* @param x {integer} mouse x position
* @param y {integer} mouse y position
* @param button {integer} button number of mouse
* @param isPressed {boolean} state of button
*/
RdpClient.prototype.sendPointerEvent = function (x, y, button, isPressed) {
if (!this.connected)
return;
var event = pdu.data.pointerEvent();
if (isPressed) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_DOWN;
}
switch(button) {
case 1:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON1;
break;
case 2:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON2;
break;
case 3:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_BUTTON3
break;
default:
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_MOVE;
}
event.obj.xPos.value = x;
event.obj.yPos.value = y;
this.global.sendInputEvents([event]);
};
/**
* send scancode event
* @param code {integer}
* @param isPressed {boolean}
* @param extended {boolenan} extended keys
*/
RdpClient.prototype.sendKeyEventScancode = function (code, isPressed, extended) {
if (!this.connected)
return;
extended = extended || false;
var event = pdu.data.scancodeKeyEvent();
event.obj.keyCode.value = code;
if (!isPressed) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE;
}
if (extended) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_EXTENDED;
}
this.global.sendInputEvents([event]);
};
/**
* Send key event as unicode
* @param code {integer}
* @param isPressed {boolean}
*/
RdpClient.prototype.sendKeyEventUnicode = function (code, isPressed) {
if (!this.connected)
return;
var event = pdu.data.unicodeKeyEvent();
event.obj.unicode.value = code;
if (!isPressed) {
event.obj.keyboardFlags.value |= pdu.data.KeyboardFlag.KBDFLAGS_RELEASE;
}
this.global.sendInputEvents([event]);
}
/**
* Wheel mouse event
* @param x {integer} mouse x position
* @param y {integer} mouse y position
* @param step {integer} wheel step
* @param isNegative {boolean}
* @param isHorizontal {boolean}
*/
RdpClient.prototype.sendWheelEvent = function (x, y, step, isNegative, isHorizontal) {
if (!this.connected)
return;
var event = pdu.data.pointerEvent();
if (isHorizontal) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_HWHEEL;
}
else {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL;
}
if (isNegative) {
event.obj.pointerFlags.value |= pdu.data.PointerFlag.PTRFLAGS_WHEEL_NEGATIVE;
}
event.obj.pointerFlags.value |= (step & pdu.data.PointerFlag.WheelRotationMask)
event.obj.xPos.value = x;
event.obj.yPos.value = y;
this.global.sendInputEvents([event]);
}
function createClient(config) {
return new RdpClient(config);
};
/**
* RDP server side protocol
* @param config {object} configuration
* @param socket {net.Socket}
*/
function RdpServer(config, socket) {
if (!(config.key && config.cert)) {
throw new error.FatalError('NODE_RDP_PROTOCOL_RDP_SERVER_CONFIG_MISSING', 'missing cryptographic tools')
}
this.connected = false;
this.bufferLayer = new layer.BufferLayer(socket);
this.tpkt = new TPKT(this.bufferLayer);
this.x224 = new x224.Server(this.tpkt, config.key, config.cert);
this.mcs = new t125.mcs.Server(this.x224);
};
inherits(RdpServer, events.EventEmitter);
function createServer (config, next) {
return net.createServer(function (socket) {
next(new RdpServer(config, socket));
});
};
/**
* Module exports
*/
module.exports = {
createClient : createClient,
createServer : createServer
};

540
rdp/protocol/t125/gcc.js Normal file
View file

@ -0,0 +1,540 @@
/*
* 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 type = require('../../core').type;
var log = require('../../core').log;
var error = require('../../core').error;
var per = require('./per');
var t124_02_98_oid = [ 0, 0, 20, 124, 0, 1 ];
var h221_cs_key = "Duca";
var h221_sc_key = "McDn";
/**
* @see http://msdn.microsoft.com/en-us/library/cc240509.aspx
*/
var MessageType = {
//server -> client
SC_CORE : 0x0C01,
SC_SECURITY : 0x0C02,
SC_NET : 0x0C03,
//client -> server
CS_CORE : 0xC001,
CS_SECURITY : 0xC002,
CS_NET : 0xC003,
CS_CLUSTER : 0xC004,
CS_MONITOR : 0xC005
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var ColorDepth = {
RNS_UD_COLOR_8BPP : 0xCA01,
RNS_UD_COLOR_16BPP_555 : 0xCA02,
RNS_UD_COLOR_16BPP_565 : 0xCA03,
RNS_UD_COLOR_24BPP : 0xCA04
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var HighColor = {
HIGH_COLOR_4BPP : 0x0004,
HIGH_COLOR_8BPP : 0x0008,
HIGH_COLOR_15BPP : 0x000f,
HIGH_COLOR_16BPP : 0x0010,
HIGH_COLOR_24BPP : 0x0018
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var Support = {
RNS_UD_24BPP_SUPPORT : 0x0001,
RNS_UD_16BPP_SUPPORT : 0x0002,
RNS_UD_15BPP_SUPPORT : 0x0004,
RNS_UD_32BPP_SUPPORT : 0x0008
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var CapabilityFlag = {
RNS_UD_CS_SUPPORT_ERRINFO_PDU : 0x0001,
RNS_UD_CS_WANT_32BPP_SESSION : 0x0002,
RNS_UD_CS_SUPPORT_STATUSINFO_PDU : 0x0004,
RNS_UD_CS_STRONG_ASYMMETRIC_KEYS : 0x0008,
RNS_UD_CS_UNUSED : 0x0010,
RNS_UD_CS_VALID_CONNECTION_TYPE : 0x0020,
RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU : 0x0040,
RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT : 0x0080,
RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL : 0x0100,
RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE : 0x0200,
RNS_UD_CS_SUPPORT_HEARTBEAT_PDU : 0x0400
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var ConnectionType = {
CONNECTION_TYPE_MODEM : 0x01,
CONNECTION_TYPE_BROADBAND_LOW : 0x02,
CONNECTION_TYPE_SATELLITE : 0x03,
CONNECTION_TYPE_BROADBAND_HIGH : 0x04,
CONNECTION_TYPE_WAN : 0x05,
CONNECTION_TYPE_LAN : 0x06,
CONNECTION_TYPE_AUTODETECT : 0x07
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
*/
var VERSION = {
RDP_VERSION_4 : 0x00080001,
RDP_VERSION_5_PLUS : 0x00080004
};
var Sequence = {
RNS_UD_SAS_DEL : 0xAA03
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240511.aspx
*/
var EncryptionMethod = {
ENCRYPTION_FLAG_40BIT : 0x00000001,
ENCRYPTION_FLAG_128BIT : 0x00000002,
ENCRYPTION_FLAG_56BIT : 0x00000008,
FIPS_ENCRYPTION_FLAG : 0x00000010
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240518.aspx
*/
var EncryptionLevel = {
ENCRYPTION_LEVEL_NONE : 0x00000000,
ENCRYPTION_LEVEL_LOW : 0x00000001,
ENCRYPTION_LEVEL_CLIENT_COMPATIBLE : 0x00000002,
ENCRYPTION_LEVEL_HIGH : 0x00000003,
ENCRYPTION_LEVEL_FIPS : 0x00000004
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240513.aspx
*/
var ChannelOptions = {
CHANNEL_OPTION_INITIALIZED : 0x80000000,
CHANNEL_OPTION_ENCRYPT_RDP : 0x40000000,
CHANNEL_OPTION_ENCRYPT_SC : 0x20000000,
CHANNEL_OPTION_ENCRYPT_CS : 0x10000000,
CHANNEL_OPTION_PRI_HIGH : 0x08000000,
CHANNEL_OPTION_PRI_MED : 0x04000000,
CHANNEL_OPTION_PRI_LOW : 0x02000000,
CHANNEL_OPTION_COMPRESS_RDP : 0x00800000,
CHANNEL_OPTION_COMPRESS : 0x00400000,
CHANNEL_OPTION_SHOW_PROTOCOL : 0x00200000,
REMOTE_CONTROL_PERSISTENT : 0x00100000
};
/**
* IBM_101_102_KEYS is the most common keyboard type
*/
var KeyboardType = {
IBM_PC_XT_83_KEY : 0x00000001,
OLIVETTI : 0x00000002,
IBM_PC_AT_84_KEY : 0x00000003,
IBM_101_102_KEYS : 0x00000004,
NOKIA_1050 : 0x00000005,
NOKIA_9140 : 0x00000006,
JAPANESE : 0x00000007
};
/**
* @see http://technet.microsoft.com/en-us/library/cc766503%28WS.10%29.aspx
*/
var KeyboardLayout = {
ARABIC : 0x00000401,
BULGARIAN : 0x00000402,
CHINESE_US_KEYBOARD : 0x00000404,
CZECH : 0x00000405,
DANISH : 0x00000406,
GERMAN : 0x00000407,
GREEK : 0x00000408,
US : 0x00000409,
SPANISH : 0x0000040a,
FINNISH : 0x0000040b,
FRENCH : 0x0000040c,
HEBREW : 0x0000040d,
HUNGARIAN : 0x0000040e,
ICELANDIC : 0x0000040f,
ITALIAN : 0x00000410,
JAPANESE : 0x00000411,
KOREAN : 0x00000412,
DUTCH : 0x00000413,
NORWEGIAN : 0x00000414
};
/**
* @see http://msdn.microsoft.com/en-us/library/cc240521.aspx
*/
var CertificateType = {
CERT_CHAIN_VERSION_1 : 0x00000001,
CERT_CHAIN_VERSION_2 : 0x00000002
};
/**
* @param {type.Type} data
* @returns {type.Component}
*/
function block(data) {
var self = {
// type of data block
type : new type.UInt16Le(function() {
return self.data.obj.__TYPE__;
}),
// length of entire packet
length : new type.UInt16Le(function() {
return new type.Component(self).size();
}),
// data block
data : data || new type.Factory(function(s){
var options = {
readLength : new type.CallableValue( function () {
return self.length.value - 4;
})
};
switch(self.type.value) {
case MessageType.SC_CORE:
self.data = serverCoreData(options).read(s);
break;
case MessageType.SC_SECURITY:
self.data = serverSecurityData(options).read(s);
break;
case MessageType.SC_NET:
self.data = serverNetworkData(null, options).read(s);
break;
case MessageType.CS_CORE:
self.data = clientCoreData(options).read(s);
break;
case MessageType.CS_SECURITY:
self.data = clientSecurityData(options).read(s);
break;
case MessageType.CS_NET:
self.data = clientNetworkData(null, options).read(s);
break;
default:
log.debug("unknown gcc block type " + self.type.value);
self.data = new type.BinaryString(null, options).read(s);
}
})
};
return new type.Component(self);
}
/**
* Main client informations
* keyboard
* screen definition
* color depth
* @see http://msdn.microsoft.com/en-us/library/cc240510.aspx
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function clientCoreData(opt) {
var self = {
__TYPE__ : MessageType.CS_CORE,
rdpVersion : new type.UInt32Le(VERSION.RDP_VERSION_5_PLUS),
desktopWidth : new type.UInt16Le(1280),
desktopHeight : new type.UInt16Le(800),
colorDepth : new type.UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP),
sasSequence : new type.UInt16Le(Sequence.RNS_UD_SAS_DEL),
kbdLayout : new type.UInt32Le(KeyboardLayout.FRENCH),
clientBuild : new type.UInt32Le(3790),
clientName : new type.BinaryString(Buffer.from('node-rdpjs\x00\x00\x00\x00\x00\x00', 'ucs2'), { readLength : new type.CallableValue(32) }),
keyboardType : new type.UInt32Le(KeyboardType.IBM_101_102_KEYS),
keyboardSubType : new type.UInt32Le(0),
keyboardFnKeys : new type.UInt32Le(12),
imeFileName : new type.BinaryString(Buffer.from(Array(64 + 1).join('\x00')), { readLength : new type.CallableValue(64), optional : true }),
postBeta2ColorDepth : new type.UInt16Le(ColorDepth.RNS_UD_COLOR_8BPP, { optional : true }),
clientProductId : new type.UInt16Le(1, { optional : true }),
serialNumber : new type.UInt32Le(0, { optional : true }),
highColorDepth : new type.UInt16Le(HighColor.HIGH_COLOR_24BPP, { optional : true }),
supportedColorDepths : new type.UInt16Le(Support.RNS_UD_15BPP_SUPPORT | Support.RNS_UD_16BPP_SUPPORT | Support.RNS_UD_24BPP_SUPPORT | Support.RNS_UD_32BPP_SUPPORT, { optional : true }),
earlyCapabilityFlags : new type.UInt16Le(CapabilityFlag.RNS_UD_CS_SUPPORT_ERRINFO_PDU, { optional : true }),
clientDigProductId : new type.BinaryString(Buffer.from(Array(64 + 1).join('\x00')), { optional : true, readLength : new type.CallableValue(64) }),
connectionType : new type.UInt8(0, { optional : true }),
pad1octet : new type.UInt8(0, { optional : true }),
serverSelectedProtocol : new type.UInt32Le(0, { optional : true })
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240517.aspx
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function serverCoreData(opt) {
var self = {
__TYPE__ : MessageType.SC_CORE,
rdpVersion : new type.UInt32Le(VERSION.RDP_VERSION_5_PLUS),
clientRequestedProtocol : new type.UInt32Le(null, { optional : true }),
earlyCapabilityFlags : new type.UInt32Le(null, { optional : true })
};
return new type.Component(self, opt);
}
/**
* @see http://msdn.microsoft.com/en-us/library/cc240511.aspx
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function clientSecurityData(opt) {
var self = {
__TYPE__ : MessageType.CS_SECURITY,
encryptionMethods : new type.UInt32Le(EncryptionMethod.ENCRYPTION_FLAG_40BIT | EncryptionMethod.ENCRYPTION_FLAG_56BIT | EncryptionMethod.ENCRYPTION_FLAG_128BIT),
extEncryptionMethods : new type.UInt32Le()
};
return new type.Component(self, opt);
}
/**
* Only use for SSL (RDP security layer TODO)
* @see http://msdn.microsoft.com/en-us/library/cc240518.aspx
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function serverSecurityData(opt) {
var self = {
__TYPE__ : MessageType.SC_SECURITY,
encryptionMethod : new type.UInt32Le(),
encryptionLevel : new type.UInt32Le()
};
return new type.Component(self, opt);
}
/**
* Channel definition
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function channelDef (opt) {
var self = {
name : new type.BinaryString(null, { readLength : new type.CallableValue(8) }),
options : new type.UInt32Le()
};
return new type.Component(self, opt);
}
/**
* Optional channel requests (sound, clipboard ...)
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function clientNetworkData(channelDefArray, opt) {
var self = {
__TYPE__ : MessageType.CS_NET,
channelCount : new type.UInt32Le( function () {
return self.channelDefArray.obj.length;
}),
channelDefArray : channelDefArray || new type.Factory( function (s) {
self.channelDefArray = new type.Component([]);
for (var i = 0; i < self.channelCount.value; i++) {
self.channelDefArray.obj.push(channelDef().read(s));
}
})
};
return new type.Component(self, opt);
}
/**
* @param channelIds {type.Component} list of available channels
* @param opt {object} Classic type options
* @returns {type.Component}
*/
function serverNetworkData (channelIds, opt) {
var self = {
__TYPE__ : MessageType.SC_NET,
MCSChannelId : new type.UInt16Le(1003, { constant : true }),
channelCount : new type.UInt16Le(function () {
return self.channelIdArray.obj.length;
}),
channelIdArray : channelIds || new type.Factory( function (s) {
self.channelIdArray = new type.Component([]);
for (var i = 0; i < self.channelCount.value; i++) {
self.channelIdArray.obj.push(new type.UInt16Le().read(s));
}
}),
pad : new type.UInt16Le(null, { conditional : function () {
return (self.channelCount.value % 2) === 1;
}})
};
return new type.Component(self, opt);
}
/**
* Client or server GCC settings block
* @param blocks {type.Component} array of gcc blocks
* @param opt {object} options to component type
* @returns {type.Component}
*/
function settings(blocks, opt) {
var self = {
blocks : blocks || new type.Factory(function(s) {
self.blocks = new type.Component([]);
// read until end of stream
while(s.availableLength() > 0) {
self.blocks.obj.push(block().read(s));
}
}),
};
return new type.Component(self, opt);
}
/**
* Read GCC response from server
* @param s {type.Stream} current stream
* @returns {Array(type.Component)} list of server block
*/
function readConferenceCreateResponse(s) {
per.readChoice(s);
if(!per.readObjectIdentifier(s, t124_02_98_oid)) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_T125_GCC_BAD_OBJECT_IDENTIFIER_T124');
}
per.readLength(s);
per.readChoice(s);
per.readInteger16(s, 1001);
per.readInteger(s);
per.readEnumerates(s);
per.readNumberOfSet(s);
per.readChoice(s);
if (!per.readOctetStream(s, h221_sc_key, 4)) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_T125_GCC_BAD_H221_SC_KEY');
}
length = per.readLength(s);
serverSettings = settings(null, { readLength : new type.CallableValue(length) });
// Object magic
return serverSettings.read(s).obj.blocks.obj.map(function(e) {
return e.obj.data;
});
}
/**
* Read GCC request
* @param s {type.Stream}
* @returns {Array(type.Component)} list of client block
*/
function readConferenceCreateRequest (s) {
per.readChoice(s);
if (!per.readObjectIdentifier(s, t124_02_98_oid)) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_T125_GCC_BAD_H221_SC_KEY');
}
per.readLength(s);
per.readChoice(s);
per.readSelection(s);
per.readNumericString(s, 1);
per.readPadding(s, 1);
if (per.readNumberOfSet(s) !== 1) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_T125_GCC_BAD_SET');
}
if (per.readChoice(s) !== 0xc0) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_T125_GCC_BAD_CHOICE');
}
per.readOctetStream(s, h221_cs_key, 4);
length = per.readLength(s);
var clientSettings = settings(null, { readLength : new type.CallableValue(length) });
// Object magic
return clientSettings.read(s).obj.blocks.obj.map(function(e) {
return e.obj.data;
});
}
/**
* Built {type.Componen} from gcc user data
* @param userData {type.Component} GCC data from client
* @returns {type.Component} GCC encoded client user data
*/
function writeConferenceCreateRequest (userData) {
var userDataStream = new type.Stream(userData.size());
userData.write(userDataStream);
return new type.Component([
per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(userData.size() + 14), per.writeChoice(0),
per.writeSelection(0x08), per.writeNumericString("1", 1), per.writePadding(1),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(Buffer.from(h221_cs_key), 4), per.writeOctetStream(userDataStream.getValue())
]);
}
function writeConferenceCreateResponse (userData) {
var userDataStream = new type.Stream(userData.size());
userData.write(userDataStream);
return new type.Component([
per.writeChoice(0), per.writeObjectIdentifier(t124_02_98_oid),
per.writeLength(userData.size() + 14), per.writeChoice(0x14),
per.writeInteger16(0x79F3, 1001), per.writeInteger(1), per.writeEnumerates(0),
per.writeNumberOfSet(1), per.writeChoice(0xc0),
per.writeOctetStream(Buffer.from(h221_sc_key), 4), per.writeOctetStream(userDataStream.getValue())
]);
}
/**
* Module exports
*/
module.exports = {
MessageType : MessageType,
VERSION : VERSION,
KeyboardLayout : KeyboardLayout,
block : block,
clientCoreData : clientCoreData,
clientNetworkData : clientNetworkData,
clientSecurityData : clientSecurityData,
serverCoreData : serverCoreData,
serverSecurityData : serverSecurityData,
serverNetworkData : serverNetworkData,
readConferenceCreateResponse : readConferenceCreateResponse,
readConferenceCreateRequest : readConferenceCreateRequest,
writeConferenceCreateRequest : writeConferenceCreateRequest,
writeConferenceCreateResponse : writeConferenceCreateResponse
};

View file

@ -0,0 +1,26 @@
/*
* 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 mcs = require('./mcs');
var gcc = require('./gcc');
module.exports = {
mcs : mcs,
gcc : gcc
};

490
rdp/protocol/t125/mcs.js Normal file
View file

@ -0,0 +1,490 @@
/*
* 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 events = require('events');
var type = require('../../core').type;
var log = require('../../core').log;
var error = require('../../core').error;
var gcc = require('./gcc');
var per = require('./per');
var asn1 = require('../../asn1');
var Message = {
MCS_TYPE_CONNECT_INITIAL : 0x65,
MCS_TYPE_CONNECT_RESPONSE : 0x66
};
var DomainMCSPDU = {
ERECT_DOMAIN_REQUEST : 1,
DISCONNECT_PROVIDER_ULTIMATUM : 8,
ATTACH_USER_REQUEST : 10,
ATTACH_USER_CONFIRM : 11,
CHANNEL_JOIN_REQUEST : 14,
CHANNEL_JOIN_CONFIRM : 15,
SEND_DATA_REQUEST : 25,
SEND_DATA_INDICATION : 26
};
var Channel = {
MCS_GLOBAL_CHANNEL : 1003,
MCS_USERCHANNEL_BASE : 1001
};
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @returns {asn1.univ.Sequence}
*/
function DomainParameters(maxChannelIds, maxUserIds, maxTokenIds,
numPriorities, minThoughput, maxHeight, maxMCSPDUsize, protocolVersion) {
return new asn1.univ.Sequence({
maxChannelIds : new asn1.univ.Integer(maxChannelIds),
maxUserIds : new asn1.univ.Integer(maxUserIds),
maxTokenIds : new asn1.univ.Integer(maxTokenIds),
numPriorities : new asn1.univ.Integer(numPriorities),
minThoughput : new asn1.univ.Integer(minThoughput),
maxHeight : new asn1.univ.Integer(maxHeight),
maxMCSPDUsize : new asn1.univ.Integer(maxMCSPDUsize),
protocolVersion : new asn1.univ.Integer(protocolVersion)
});
}
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @param userData {Buffer}
* @returns {asn1.univ.Sequence}
*/
function ConnectInitial (userData) {
return new asn1.univ.Sequence({
callingDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
calledDomainSelector : new asn1.univ.OctetString(Buffer.from('\x01', 'binary')),
upwardFlag : new asn1.univ.Boolean(true),
targetParameters : DomainParameters(34, 2, 0, 1, 0, 1, 0xffff, 2),
minimumParameters : DomainParameters(1, 1, 1, 1, 0, 1, 0x420, 2),
maximumParameters : DomainParameters(0xffff, 0xfc17, 0xffff, 1, 0, 1, 0xffff, 2),
userData : new asn1.univ.OctetString(userData)
}).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 101));
}
/**
* @see http://www.itu.int/rec/T-REC-T.125-199802-I/en page 25
* @returns {asn1.univ.Sequence}
*/
function ConnectResponse (userData) {
return new asn1.univ.Sequence({
result : new asn1.univ.Enumerate(0),
calledConnectId : new asn1.univ.Integer(0),
domainParameters : DomainParameters(22, 3, 0, 1, 0, 1,0xfff8, 2),
userData : new asn1.univ.OctetString(userData)
}).implicitTag(new asn1.spec.Asn1Tag(asn1.spec.TagClass.Application, asn1.spec.TagFormat.Constructed, 102));
}
/**
* Format MCS PDU header packet
* @param mcsPdu {integer}
* @param options {integer}
* @returns {type.UInt8} headers
*/
function writeMCSPDUHeader(mcsPdu, options) {
options = options || 0;
return new type.UInt8((mcsPdu << 2) | options);
}
/**
* Read MCS PDU header
* @param opcode
* @param mcsPdu
* @returns {Boolean}
*/
function readMCSPDUHeader(opcode, mcsPdu) {
return (opcode >> 2) === mcsPdu;
}
/**
* Multi-Channel Services
* @param transport {events.EventEmitter} transport layer listen (connect, data) events
* @param recvOpCode {DomainMCSPDU} opcode use in receive automata
* @param sendOpCode {DomainMCSPDU} opcode use to send message
*/
function MCS(transport, recvOpCode, sendOpCode) {
this.transport = transport;
this.recvOpCode = recvOpCode;
this.sendOpCode = sendOpCode;
this.channels = [{id : Channel.MCS_GLOBAL_CHANNEL, name : 'global'}];
this.channels.find = function(callback) {
for(var i in this) {
if(callback(this[i])) return this[i];
};
};
// bind events
var self = this;
this.transport.on('close', function () {
self.emit('close');
}).on('error', function (err) {
self.emit('error', err);
});
}
//inherit from Layer
inherits(MCS, events.EventEmitter);
/**
* Send message to a specific channel
* @param channelName {string} name of channel
* @param data {type.*} message to send
*/
MCS.prototype.send = function(channelName, data) {
var channelId = this.channels.find(function(element) {
if (element.name === channelName) return true;
}).id;
this.transport.send(new type.Component([
writeMCSPDUHeader(this.sendOpCode),
per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId),
new type.UInt8(0x70),
per.writeLength(data.size()),
data
]));
};
/**
* Main receive function
* @param s {type.Stream}
*/
MCS.prototype.recv = function(s) {
opcode = new type.UInt8().read(s).value;
if (readMCSPDUHeader(opcode, DomainMCSPDU.DISCONNECT_PROVIDER_ULTIMATUM)) {
log.info("MCS DISCONNECT_PROVIDER_ULTIMATUM");
this.transport.close();
return
}
else if(!readMCSPDUHeader(opcode, this.recvOpCode)) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_RECEIVE_OPCODE');
}
per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
var channelId = per.readInteger16(s);
per.readEnumerates(s);
per.readLength(s);
var channelName = this.channels.find(function(e) {
if (e.id === channelId) return true;
}).name;
this.emit(channelName, s);
};
/**
* Only main channels handle actually
* @param transport {event.EventEmitter} bind connect and data events
* @returns
*/
function Client(transport) {
MCS.call(this, transport, DomainMCSPDU.SEND_DATA_INDICATION, DomainMCSPDU.SEND_DATA_REQUEST);
// channel context automata
this.channelsConnected = 0;
// init gcc information
this.clientCoreData = gcc.clientCoreData();
this.clientNetworkData = gcc.clientNetworkData(new type.Component([]));
this.clientSecurityData = gcc.clientSecurityData();
// must be readed from protocol
this.serverCoreData = null;
this.serverSecurityData = null;
this.serverNetworkData = null;
var self = this;
this.transport.on('connect', function(s) {
self.connect(s);
});
}
inherits(Client, MCS);
/**
* Connect event layer
*/
Client.prototype.connect = function(selectedProtocol) {
this.clientCoreData.obj.serverSelectedProtocol.value = selectedProtocol;
this.sendConnectInitial();
};
/**
* close stack
*/
Client.prototype.close = function() {
this.transport.close();
};
/**
* MCS connect response (server->client)
* @param s {type.Stream}
*/
Client.prototype.recvConnectResponse = function(s) {
var userData = new type.Stream(ConnectResponse().decode(s, asn1.ber).value.userData.value);
var serverSettings = gcc.readConferenceCreateResponse(userData);
// record server gcc block
for(var i in serverSettings) {
if(!serverSettings[i].obj) {
continue;
}
switch(serverSettings[i].obj.__TYPE__) {
case gcc.MessageType.SC_CORE:
this.serverCoreData = serverSettings[i];
break;
case gcc.MessageType.SC_SECURITY:
this.serverSecurityData = serverSettings[i];
break;
case gcc.MessageType.SC_NET:
this.serverNetworkData = serverSettings[i];
break;
default:
log.warn('unhandle server gcc block : ' + serverSettings[i].obj.__TYPE__);
}
}
// send domain request
this.sendErectDomainRequest();
// send attach user request
this.sendAttachUserRequest();
// now wait user confirm from server
var self = this;
this.transport.once('data', function(s) {
self.recvAttachUserConfirm(s);
});
};
/**
* MCS connection automata step
* @param s {type.Stream}
*/
Client.prototype.recvAttachUserConfirm = function(s) {
if (!readMCSPDUHeader(new type.UInt8().read(s).value, DomainMCSPDU.ATTACH_USER_CONFIRM)) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_BAD_HEADER');
}
if (per.readEnumerates(s) !== 0) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_REJECT_USER');
}
this.userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
//ask channel for specific user
this.channels.push({ id : this.userId, name : 'user' });
// channel connect automata
this.connectChannels();
};
/**
* Last state in channel connection automata
* @param s {type.Stream}
*/
Client.prototype.recvChannelJoinConfirm = function(s) {
var opcode = new type.UInt8().read(s).value;
if (!readMCSPDUHeader(opcode, DomainMCSPDU.CHANNEL_JOIN_CONFIRM)) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_WAIT_CHANNEL_JOIN_CONFIRM');
}
var confirm = per.readEnumerates(s);
var userId = per.readInteger16(s, Channel.MCS_USERCHANNEL_BASE);
if (this.userId !== userId) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_INVALID_USER_ID');
}
var channelId = per.readInteger16(s);
if ((confirm !== 0) && (channelId === Channel.MCS_GLOBAL_CHANNEL || channelId === this.userId)) {
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_MCS_SERVER_MUST_CONFIRM_STATIC_CHANNEL');
}
this.connectChannels();
};
/**
* First MCS message
*/
Client.prototype.sendConnectInitial = function() {
var ccReq = gcc.writeConferenceCreateRequest(new type.Component([
gcc.block(this.clientCoreData),
gcc.block(this.clientNetworkData),
gcc.block(this.clientSecurityData)
])).toStream().getValue();
this.transport.send(ConnectInitial(ccReq).encode(asn1.ber));
// next event is connect response
var self = this;
this.transport.once('data', function(s) {
self.recvConnectResponse(s);
});
};
/**
* MCS connection automata step
*/
Client.prototype.sendErectDomainRequest = function() {
this.transport.send(new type.Component([
writeMCSPDUHeader(DomainMCSPDU.ERECT_DOMAIN_REQUEST),
per.writeInteger(0),
per.writeInteger(0)
]));
};
/**
* MCS connection automata step
*/
Client.prototype.sendAttachUserRequest = function() {
this.transport.send(writeMCSPDUHeader(DomainMCSPDU.ATTACH_USER_REQUEST));
};
/**
* Send a channel join request
* @param channelId {integer} channel id
*/
Client.prototype.sendChannelJoinRequest = function(channelId) {
this.transport.send(new type.Component([
writeMCSPDUHeader(DomainMCSPDU.CHANNEL_JOIN_REQUEST),
per.writeInteger16(this.userId, Channel.MCS_USERCHANNEL_BASE),
per.writeInteger16(channelId)
]));
};
/**
* Connect channels automata
* @param s {type.Stream}
*/
Client.prototype.connectChannels = function(s) {
if(this.channelsConnected == this.channels.length) {
var self = this;
this.transport.on('data', function(s) {
self.recv(s);
});
// send client and sever gcc informations
this.emit('connect',
{
core : this.clientCoreData.obj,
security : this.clientSecurityData.obj,
net : this.clientNetworkData.obj
},
{
core : this.serverCoreData.obj,
security : this.serverSecurityData.obj
}, this.userId, this.channels);
return;
}
this.sendChannelJoinRequest(this.channels[this.channelsConnected++].id);
var self = this;
this.transport.once('data', function(s) {
self.recvChannelJoinConfirm(s);
});
};
/**
* Server side of MCS layer
* @param transport
*/
function Server (transport) {
MCS.call(this, transport, DomainMCSPDU.SEND_DATA_REQUEST, DomainMCSPDU.SEND_DATA_INDICATION);
// must be readed from protocol
this.clientCoreData = null;
this.clientNetworkData = null;
this.clientSecurityData = null;
// init gcc information
this.serverCoreData = gcc.serverCoreData();
this.serverSecurityData = gcc.serverSecurityData();
this.serverNetworkData = gcc.serverNetworkData(new type.Component([]));
var self = this;
this.transport.on('connect', function (selectedProtocol) {
self.serverCoreData.obj.clientRequestedProtocol.value = selectedProtocol;
}).once('data', function (s) {
self.recvConnectInitial(s);
});
}
inherits(Server, MCS);
/**
* First state of server automata
* @param s {type.Stream}
*/
Server.prototype.recvConnectInitial = function (s) {
var userData = new type.Stream(ConnectInitial().decode(s, asn1.ber).value.userData.value);
var clientSettings = gcc.readConferenceCreateRequest(userData);
// record server gcc block
for(var i in clientSettings) {
if(!clientSettings[i].obj) {
continue;
}
switch(clientSettings[i].obj.__TYPE__) {
case gcc.MessageType.CS_CORE:
this.clientCoreData = clientSettings[i];
break;
case gcc.MessageType.CS_SECURITY:
this.clientSecurityData = clientSettings[i];
break;
case gcc.MessageType.CS_NET:
this.clientNetworkData = clientSettings[i];
for (var i = 0; i < this.clientNetworkData.obj.channelCount.value; i++) {
this.serverNetworkData.obj.channelIdArray.obj.push(new type.UInt16Le( i + 1 + Channel.MCS_GLOBAL_CHANNEL));
}
break;
default:
log.debug('unhandle client gcc block : ' + clientSettings[i].obj.__TYPE__);
}
}
this.sendConnectResponse();
};
/**
* State 2 in mcs server connetion automata
*/
Server.prototype.sendConnectResponse = function () {
var ccReq = gcc.writeConferenceCreateResponse(new type.Component([
gcc.block(this.serverCoreData),
gcc.block(this.serverSecurityData),
gcc.block(this.serverNetworkData),
])).toStream().getValue();
this.transport.send(ConnectResponse(ccReq).encode(asn1.ber));
};
/**
* Module exports
*/
module.exports = {
Client : Client,
Server : Server
};

338
rdp/protocol/t125/per.js Normal file
View file

@ -0,0 +1,338 @@
/*
* 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 type = require('../../core').type;
var error = require('../../core').error;
/**
* @param s {type.Stream} read value from stream
* @returns read length from per format
*/
function readLength(s) {
var byte = new type.UInt8().read(s).value;
var size = 0;
if(byte & 0x80) {
byte &= ~0x80;
size = byte << 8;
size += new type.UInt8().read(s).value;
}
else {
size = byte;
}
return size;
}
/**
* @param value {raw} value to convert to per format
* @returns type objects per encoding value
*/
function writeLength(value) {
if(value > 0x7f) {
return new type.UInt16Be(value | 0x8000);
}
else {
return new type.UInt8(value);
}
}
/**
* @param s {type.Stream}
* @returns {integer} choice decoding from per encoding
*/
function readChoice(s) {
return new type.UInt8().read(s).value;
}
/**
* @param choice {integer}
* @returns {type.UInt8} choice per encoded
*/
function writeChoice(choice) {
return new type.UInt8(choice);
}
/**
* @param s {type.Stream}
* @returns {integer} number represent selection
*/
function readSelection(s) {
return new type.UInt8().read(s).value;
}
/**
* @param selection {integer}
* @returns {type.UInt8} per encoded selection
*/
function writeSelection(selection) {
return new type.UInt8(selection);
}
/**
* @param s {type.Stream}
* @returns {integer} number of sets
*/
function readNumberOfSet(s) {
return new type.UInt8().read(s).value;
}
/**
* @param numberOfSet {integer}
* @returns {type.UInt8} per encoded nuimber of sets
*/
function writeNumberOfSet(numberOfSet) {
return new type.UInt8(numberOfSet);
}
/**
* @param s {type.Stream}
* @returns {integer} enumerates number
*/
function readEnumerates(s) {
return new type.UInt8().read(s).value;
}
/**
* @param enumerate {integer}
* @returns {type.UInt8} per encoded enumerate
*/
function writeEnumerates(enumerate) {
return new type.UInt8(enumerate);
}
/**
* @param s {type.Stream}
* @returns {integer} integer per decoded
*/
function readInteger(s) {
var result;
var size = readLength(s);
switch(size) {
case 1:
result = new type.UInt8();
break;
case 2:
result = new type.UInt16Be();
break;
case 4:
result = new type.UInt32Be();
break;
default:
throw new error.UnexpectedFatalError('NODE_RDP_PROTOCOL_T125_PER_BAD_INTEGER_LENGTH');
}
return result.read(s).value;
}
/**
* @param value {integer}
* @returns {type.Component} per encoded integer
*/
function writeInteger(value) {
if(value <= 0xff) {
return new type.Component([writeLength(1), new type.UInt8(value)]);
}
else if(value < 0xffff) {
return new type.Component([writeLength(2), new type.UInt16Be(value)]);
}
else {
return new type.Component([writeLength(4), new type.UInt32Be(value)]);
}
}
/**
* @param s {type.Stream}
* @param minimum {integer} increment (default 0)
* @returns {integer} per decoded integer 16 bits
*/
function readInteger16(s, minimum) {
return new type.UInt16Be().read(s).value + (minimum || 0);
}
/**
* @param value {integer}
* @param minimum {integer} decrement (default 0)
* @returns {type.UInt16Be} per encoded integer 16 bits
*/
function writeInteger16(value, minimum) {
return new type.UInt16Be(value - (minimum || 0));
}
/**
* Check object identifier
* @param s {type.Stream}
* @param oid {array} object identifier to check
*/
function readObjectIdentifier(s, oid) {
var size = readLength(s);
if(size !== 5) {
return false;
}
var a_oid = [0, 0, 0, 0, 0, 0];
var t12 = new type.UInt8().read(s).value;
a_oid[0] = t12 >> 4;
a_oid[1] = t12 & 0x0f;
a_oid[2] = new type.UInt8().read(s).value;
a_oid[3] = new type.UInt8().read(s).value;
a_oid[4] = new type.UInt8().read(s).value;
a_oid[5] = new type.UInt8().read(s).value;
for(var i in oid) {
if(oid[i] !== a_oid[i]) return false;
}
return true;
}
/**
* @param oid {array} oid to write
* @returns {type.Component} per encoded object identifier
*/
function writeObjectIdentifier(oid) {
return new type.Component([new type.UInt8(5), new type.UInt8((oid[0] << 4) & (oid[1] & 0x0f)), new type.UInt8(oid[2]), new type.UInt8(oid[3]), new type.UInt8(oid[4]), new type.UInt8(oid[5])]);
}
/**
* Read as padding...
* @param s {type.Stream}
* @param minValue
*/
function readNumericString(s, minValue) {
var length = readLength(s);
length = (length + minValue + 1) / 2;
s.readPadding(length);
}
/**
* @param nStr {String}
* @param minValue {integer}
* @returns {type.Component} per encoded numeric string
*/
function writeNumericString(nStr, minValue) {
var length = nStr.length;
var mlength = minValue;
if(length - minValue >= 0) {
mlength = length - minValue;
}
var result = [];
for(var i = 0; i < length; i += 2) {
var c1 = nStr.charCodeAt(i);
var c2 = 0;
if(i + 1 < length) {
c2 = nStr.charCodeAt(i + 1);
}
else {
c2 = 0x30;
}
c1 = (c1 - 0x30) % 10;
c2 = (c2 - 0x30) % 10;
result[result.length] = new type.UInt8((c1 << 4) | c2);
}
return new type.Component([writeLength(mlength), new type.Component(result)]);
}
/**
* @param s {type.Stream}
* @param length {integer} length of padding
*/
function readPadding(s, length) {
s.readPadding(length);
}
/**
* @param length {integer} length of padding
* @returns {type.BinaryString} per encoded padding
*/
function writePadding(length) {
return new type.BinaryString(Buffer.from(Array(length + 1).join("\x00")));
}
/**
* @param s {type.Stream}
* @param octetStream {String}
* @param minValue {integer} default 0
* @returns {Boolean} true if read octectStream is equal to octetStream
*/
function readOctetStream(s, octetStream, minValue) {
var size = readLength(s) + (minValue || 0);
if(size !== octetStream.length) {
return false;
}
for(var i = 0; i < size; i++) {
var c = new type.UInt8().read(s);
if(octetStream.charCodeAt(i) !== c.value) {
return false;
}
}
return true;
}
/**
* @param oStr {String}
* @param minValue {integer} default 0
* @returns {type.Component} per encoded octet stream
*/
function writeOctetStream(oStr, minValue) {
minValue = minValue || 0;
var length = oStr.length;
var mlength = minValue;
if(length - minValue >= 0) {
mlength = length - minValue;
}
result = [];
for(var i = 0; i < length; i++) {
result[result.length] = new type.UInt8(oStr[i]);
}
return new type.Component([writeLength(mlength), new type.Component(result)]);
}
/**
* Module exports
*/
module.exports = {
readLength : readLength,
writeLength : writeLength,
readChoice : readChoice,
writeChoice : writeChoice,
readSelection : readSelection,
writeSelection : writeSelection,
readNumberOfSet : readNumberOfSet,
writeNumberOfSet : writeNumberOfSet,
readEnumerates : readEnumerates,
writeEnumerates : writeEnumerates,
readInteger : readInteger,
writeInteger : writeInteger,
readInteger16 : readInteger16,
writeInteger16 : writeInteger16,
readObjectIdentifier : readObjectIdentifier,
writeObjectIdentifier : writeObjectIdentifier,
readNumericString : readNumericString,
writeNumericString : writeNumericString,
readPadding : readPadding,
writePadding : writePadding,
readOctetStream : readOctetStream,
writeOctetStream : writeOctetStream
};

171
rdp/protocol/tpkt.js Normal file
View file

@ -0,0 +1,171 @@
/*
* 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 type = require('../core').type;
var events = require('events');
/**
* Type of tpkt packet
* Fastpath is use to shortcut RDP stack
* @see http://msdn.microsoft.com/en-us/library/cc240621.aspx
* @see http://msdn.microsoft.com/en-us/library/cc240589.aspx
*/
var Action = {
FASTPATH_ACTION_FASTPATH : 0x0,
FASTPATH_ACTION_X224 : 0x3
};
/**
* TPKT layer of rdp stack
*/
function TPKT(transport) {
this.transport = transport;
// wait 2 bytes
this.transport.expect(2);
// next state is receive header
var self = this;
this.transport.once('data', function(s) {
self.recvHeader(s);
}).on('close', function() {
self.emit('close');
}).on('error', function (err) {
self.emit('error', err);
});
}
/**
* inherit from a packet layer
*/
inherits(TPKT, events.EventEmitter);
/**
* Receive correct packet as expected
* @param s {type.Stream}
*/
TPKT.prototype.recvHeader = function (s) {
var version = new type.UInt8().read(s).value;
var self = this;
if(version === Action.FASTPATH_ACTION_X224) {
new type.UInt8().read(s);
this.transport.expect(2);
this.transport.once('data', function(s) {
self.recvExtendedHeader(s);
});
}
else {
this.secFlag = ((version >> 6) & 0x3);
var length = new type.UInt8().read(s).value;
if (length & 0x80) {
this.transport.expect(1);
this.transport.once('data', function(s) {
self.recvExtendedFastPathHeader(s, length);
});
}
else {
this.transport.expect(length - 2);
this.transport.once('data', function(s) {
self.recvFastPath(s);
});
}
}
};
/**
* Receive second part of header packet
* @param s {type.Stream}
*/
TPKT.prototype.recvExtendedHeader = function (s) {
var size = new type.UInt16Be().read(s);
this.transport.expect(size.value - 4);
//next state receive packet
var self = this;
this.transport.once('data', function(s) {
self.recvData(s);
});
};
/**
* Receive data available for presentation layer
* @param s {type.Stream}
*/
TPKT.prototype.recvData = function (s) {
this.emit('data', s);
this.transport.expect(2);
//next state receive header
var self = this;
this.transport.once('data', function(s) {
self.recvHeader(s);
});
};
/**
* Read extended fastpath header
* @param s {type.Stream}
*/
TPKT.prototype.recvExtendedFastPathHeader = function (s, length) {
var rightPart = new type.UInt8().read(s).value;
var leftPart = length & ~0x80;
var packetSize = (leftPart << 8) + rightPart;
var self = this;
this.transport.expect(packetSize - 3);
this.transport.once('data', function(s) {
self.recvFastPath(s);
});
};
/**
* Read fast path data
* @param s {type.Stream}
*/
TPKT.prototype.recvFastPath = function (s) {
this.emit('fastPathData', this.secFlag, s);
var self = this;
this.transport.expect(2);
this.transport.once('data', function(s) {
self.recvHeader(s);
});
};
/**
* Send message throught TPKT layer
* @param message {type.*}
*/
TPKT.prototype.send = function(message) {
this.transport.send(new type.Component([
new type.UInt8(Action.FASTPATH_ACTION_X224),
new type.UInt8(0),
new type.UInt16Be(message.size() + 4),
message
]));
};
/**
* close stack
*/
TPKT.prototype.close = function() {
this.transport.close();
};
/**
* Module exports
*/
module.exports = TPKT;

346
rdp/protocol/x224.js Normal file
View file

@ -0,0 +1,346 @@
/*
* 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 events = require('events');
var type = require('../core').type;
var log = require('../core').log;
var error = require('../core').error;
/**
* Message type present in X224 packet header
*/
var MessageType = {
X224_TPDU_CONNECTION_REQUEST : 0xE0,
X224_TPDU_CONNECTION_CONFIRM : 0xD0,
X224_TPDU_DISCONNECT_REQUEST : 0x80,
X224_TPDU_DATA : 0xF0,
X224_TPDU_ERROR : 0x70
};
/**
* Type of negotiation present in negotiation packet
*/
var NegotiationType = {
TYPE_RDP_NEG_REQ : 0x01,
TYPE_RDP_NEG_RSP : 0x02,
TYPE_RDP_NEG_FAILURE : 0x03
};
/**
* Protocols available for x224 layer
*/
var Protocols = {
PROTOCOL_RDP : 0x00000000,
PROTOCOL_SSL : 0x00000001,
PROTOCOL_HYBRID : 0x00000002,
PROTOCOL_HYBRID_EX : 0x00000008
};
/**
* Use to negotiate security layer of RDP stack
* In node-rdpjs only ssl is available
* @param opt {object} component type options
* @see request -> http://msdn.microsoft.com/en-us/library/cc240500.aspx
* @see response -> http://msdn.microsoft.com/en-us/library/cc240506.aspx
* @see failure ->http://msdn.microsoft.com/en-us/library/cc240507.aspx
*/
function negotiation(opt) {
var self = {
type : new type.UInt8(),
flag : new type.UInt8(),
length : new type.UInt16Le(0x0008, { constant : true }),
result : new type.UInt32Le()
};
return new type.Component(self, opt);
}
/**
* X224 client connection request
* @param opt {object} component type options
* @see http://msdn.microsoft.com/en-us/library/cc240470.aspx
*/
function clientConnectionRequestPDU(opt, cookie) {
var self = {
len : new type.UInt8(function() {
return new type.Component(self).size() - 1;
}),
code : new type.UInt8(MessageType.X224_TPDU_CONNECTION_REQUEST, { constant : true }),
padding : new type.Component([new type.UInt16Le(), new type.UInt16Le(), new type.UInt8()]),
cookie : cookie || new type.Factory( function (s) {
var offset = 0;
while (true) {
var token = s.buffer.readUInt16LE(s.offset + offset);
if (token === 0x0a0d) {
self.cookie = new type.BinaryString(null, { readLength : new type.CallableValue(offset + 2) }).read(s);
return;
}
else {
offset += 1;
}
}
}, { conditional : function () {
return self.len.value > 14;
}}),
protocolNeg : negotiation({ optional : true })
};
return new type.Component(self, opt);
}
/**
* X224 Server connection confirm
* @param opt {object} component type options
* @see http://msdn.microsoft.com/en-us/library/cc240506.aspx
*/
function serverConnectionConfirm(opt) {
var self = {
len : new type.UInt8(function() {
return new type.Component(self).size() - 1;
}),
code : new type.UInt8(MessageType.X224_TPDU_CONNECTION_CONFIRM, { constant : true }),
padding : new type.Component([new type.UInt16Le(), new type.UInt16Le(), new type.UInt8()]),
protocolNeg : negotiation({ optional : true })
};
return new type.Component(self, opt);
}
/**
* Header of each data message from x224 layer
* @returns {type.Component}
*/
function x224DataHeader() {
var self = {
header : new type.UInt8(2),
messageType : new type.UInt8(MessageType.X224_TPDU_DATA, { constant : true }),
separator : new type.UInt8(0x80, { constant : true })
};
return new type.Component(self);
}
/**
* Common X224 Automata
* @param presentation {Layer} presentation layer
*/
function X224(transport) {
this.transport = transport;
this.requestedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID;
this.selectedProtocol = Protocols.PROTOCOL_SSL | Protocols.PROTOCOL_HYBRID;
var self = this;
this.transport.on('close', function() {
self.emit('close');
}).on('error', function (err) {
self.emit('error', err);
});
}
//inherit from Layer
inherits(X224, events.EventEmitter);
/**
* Main data received function
* after connection sequence
* @param s {type.Stream} stream formated from transport layer
*/
X224.prototype.recvData = function(s) {
// check header
x224DataHeader().read(s);
this.emit('data', s);
};
/**
* Format message from x224 layer to transport layer
* @param message {type}
* @returns {type.Component} x224 formated message
*/
X224.prototype.send = function(message) {
this.transport.send(new type.Component([x224DataHeader(), message]));
};
/**
* Client x224 automata
* @param transport {events.EventEmitter} (bind data events)
*/
function Client(transport, config) {
this.config = config;
X224.call(this, transport);
}
//inherit from X224 automata
inherits(Client, X224);
/**
* Client automata connect event
*/
Client.prototype.connect = function() {
var message = clientConnectionRequestPDU(null, new type.BinaryString());
message.obj.protocolNeg.obj.type.value = NegotiationType.TYPE_RDP_NEG_REQ;
message.obj.protocolNeg.obj.result.value = this.requestedProtocol;
this.transport.send(message);
// next state wait connection confirm packet
var self = this;
this.transport.once('data', function(s) {
self.recvConnectionConfirm(s);
});
};
/**
* close stack
*/
Client.prototype.close = function() {
this.transport.close();
};
/**
* Receive connection from server
* @param s {Stream}
*/
Client.prototype.recvConnectionConfirm = function(s) {
var message = serverConnectionConfirm().read(s);
if (message.obj.protocolNeg.obj.type.value == NegotiationType.TYPE_RDP_NEG_FAILURE) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_X224_NEG_FAILURE',
'Failure code:' + message.obj.protocolNeg.obj.result.value + " (see https://msdn.microsoft.com/en-us/library/cc240507.aspx)");
}
if (message.obj.protocolNeg.obj.type.value == NegotiationType.TYPE_RDP_NEG_RSP) {
this.selectedProtocol = message.obj.protocolNeg.obj.result.value;
}
if ([Protocols.PROTOCOL_HYBRID_EX].indexOf(this.selectedProtocol) !== -1) {
throw new error.ProtocolError('NODE_RDP_PROTOCOL_X224_NLA_NOT_SUPPORTED');
}
if (this.selectedProtocol == Protocols.PROTOCOL_RDP) {
log.debug("RDP standard security selected");
return;
}
if (this.selectedProtocol == Protocols.PROTOCOL_HYBRID) {
log.debug("NLA security layer selected");
var self = this;
var transportEx = this.transport.transport;
this.transport.transport.startTLS(function () {
//console.log('TLS connected, start cssp_connect()');
var NLA = require('./nla');
self.nla = new NLA(transportEx, function () { self.nlaCompleted(); }, self.config.domain, self.config.userName, self.config.password);
self.nla.sendNegotiateMessage();
});
return;
}
// finish connection sequence
var self = this;
this.transport.on('data', function(s) {
self.recvData(s);
});
if (this.selectedProtocol == Protocols.PROTOCOL_SSL) {
log.debug("SSL standard security selected");
this.transport.transport.startTLS(function() {
self.emit('connect', self.selectedProtocol);
});
return;
}
};
/**
* Called when NLA is completed
*/
Client.prototype.nlaCompleted = function () {
const self = this;
delete self.nla;
this.transport.on('data', function (s) { self.recvData(s); });
this.emit('connect', this.selectedProtocol);
}
/**
* Server x224 automata
*/
function Server(transport, keyFilePath, crtFilePath) {
X224.call(this, transport);
this.keyFilePath = keyFilePath;
this.crtFilePath = crtFilePath;
var self = this;
this.transport.once('data', function (s) {
self.recvConnectionRequest(s);
});
}
//inherit from X224 automata
inherits(Server, X224);
/**
* @see http://msdn.microsoft.com/en-us/library/cc240470.aspx
* @param s {type.Stream}
*/
Server.prototype.recvConnectionRequest = function (s) {
var request = clientConnectionRequestPDU().read(s);
if (!request.obj.protocolNeg.isReaded) {
throw new Error('NODE_RDP_PROTOCOL_X224_NO_BASIC_SECURITY_LAYER');
}
this.requestedProtocol = request.obj.protocolNeg.obj.result.value;
this.selectedProtocol = this.requestedProtocol & Protocols.PROTOCOL_SSL;
if (!(this.selectedProtocol & Protocols.PROTOCOL_SSL)) {
var confirm = serverConnectionConfirm();
confirm.obj.protocolNeg.obj.type.value = NegociationType.TYPE_RDP_NEG_FAILURE;
confirm.obj.protocolNeg.obj.result.value = NegotiationFailureCode.SSL_REQUIRED_BY_SERVER;
this.transport.send(confirm);
this.close();
}
else {
this.sendConnectionConfirm();
}
};
/**
* Start SSL connection if needed
* @see http://msdn.microsoft.com/en-us/library/cc240501.aspx
*/
Server.prototype.sendConnectionConfirm = function () {
var confirm = serverConnectionConfirm();
confirm.obj.protocolNeg.obj.type.value = NegotiationType.TYPE_RDP_NEG_RSP;
confirm.obj.protocolNeg.obj.result.value = this.selectedProtocol;
this.transport.send(confirm);
// finish connection sequence
var self = this;
this.transport.on('data', function(s) {
self.recvData(s);
});
this.transport.transport.listenTLS(this.keyFilePath, this.crtFilePath, function() {
log.debug('start SSL connection');
self.emit('connect', self.requestedProtocol);
});
};
/**
* Module exports
*/
module.exports = {
Client : Client,
Server : Server
};