ai-remote 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +7 -0
- package/dist/protocols/index.js +35 -0
- package/dist/protocols/index.js.map +7 -0
- package/dist/protocols/rdp/buffer.js +170 -0
- package/dist/protocols/rdp/buffer.js.map +7 -0
- package/dist/protocols/rdp/caps.js +260 -0
- package/dist/protocols/rdp/caps.js.map +7 -0
- package/dist/protocols/rdp/cert.js +135 -0
- package/dist/protocols/rdp/cert.js.map +7 -0
- package/dist/protocols/rdp/client.js +1068 -0
- package/dist/protocols/rdp/client.js.map +7 -0
- package/dist/protocols/rdp/cliprdr.js +240 -0
- package/dist/protocols/rdp/cliprdr.js.map +7 -0
- package/dist/protocols/rdp/credssp.js +254 -0
- package/dist/protocols/rdp/credssp.js.map +7 -0
- package/dist/protocols/rdp/crypto.js +369 -0
- package/dist/protocols/rdp/crypto.js.map +7 -0
- package/dist/protocols/rdp/display.js +589 -0
- package/dist/protocols/rdp/display.js.map +7 -0
- package/dist/protocols/rdp/gcc.js +132 -0
- package/dist/protocols/rdp/gcc.js.map +7 -0
- package/dist/protocols/rdp/keymap.js +132 -0
- package/dist/protocols/rdp/keymap.js.map +7 -0
- package/dist/protocols/rdp/mcs.js +223 -0
- package/dist/protocols/rdp/mcs.js.map +7 -0
- package/dist/protocols/rdp/ntlm.js +305 -0
- package/dist/protocols/rdp/ntlm.js.map +7 -0
- package/dist/protocols/rdp/pdu.js +444 -0
- package/dist/protocols/rdp/pdu.js.map +7 -0
- package/dist/protocols/rdp/rail.js +383 -0
- package/dist/protocols/rdp/rail.js.map +7 -0
- package/dist/protocols/rdp/rle.js +276 -0
- package/dist/protocols/rdp/rle.js.map +7 -0
- package/dist/protocols/rdp/sec.js +156 -0
- package/dist/protocols/rdp/sec.js.map +7 -0
- package/dist/protocols/rdp/session.js +307 -0
- package/dist/protocols/rdp/session.js.map +7 -0
- package/dist/protocols/rdp/tls.js +354 -0
- package/dist/protocols/rdp/tls.js.map +7 -0
- package/dist/protocols/rdp/vchannel.js +59 -0
- package/dist/protocols/rdp/vchannel.js.map +7 -0
- package/dist/protocols/rdp/x224.js +110 -0
- package/dist/protocols/rdp/x224.js.map +7 -0
- package/dist/protocols/ssh/kex.js +177 -0
- package/dist/protocols/ssh/kex.js.map +7 -0
- package/dist/protocols/ssh/messages.js +55 -0
- package/dist/protocols/ssh/messages.js.map +7 -0
- package/dist/protocols/ssh/session.js +608 -0
- package/dist/protocols/ssh/session.js.map +7 -0
- package/dist/protocols/ssh/terminal.js +191 -0
- package/dist/protocols/ssh/terminal.js.map +7 -0
- package/dist/protocols/ssh/transport.js +611 -0
- package/dist/protocols/ssh/transport.js.map +7 -0
- package/dist/protocols/ssh/wire.js +189 -0
- package/dist/protocols/ssh/wire.js.map +7 -0
- package/dist/protocols/types.js +1 -0
- package/dist/protocols/types.js.map +7 -0
- package/dist/protocols/vnc/input.js +16 -0
- package/dist/protocols/vnc/input.js.map +7 -0
- package/dist/protocols/vnc/session.js +259 -0
- package/dist/protocols/vnc/session.js.map +7 -0
- package/dist/shared/connection.js +114 -0
- package/dist/shared/connection.js.map +7 -0
- package/dist/shared/device.js +1 -0
- package/dist/shared/device.js.map +7 -0
- package/dist/shared/hosts.js +133 -0
- package/dist/shared/hosts.js.map +7 -0
- package/dist/shared/icons.js +42 -0
- package/dist/shared/icons.js.map +7 -0
- package/dist/shared/protocol.js +40 -0
- package/dist/shared/protocol.js.map +7 -0
- package/dist/shared/signals.js +42 -0
- package/dist/shared/signals.js.map +7 -0
- package/package.json +82 -0
- package/src/index.ts +14 -0
- package/src/protocols/index.ts +72 -0
- package/src/protocols/rdp/buffer.ts +204 -0
- package/src/protocols/rdp/caps.ts +304 -0
- package/src/protocols/rdp/cert.ts +190 -0
- package/src/protocols/rdp/client.ts +1363 -0
- package/src/protocols/rdp/cliprdr.ts +344 -0
- package/src/protocols/rdp/credssp.ts +319 -0
- package/src/protocols/rdp/crypto.ts +416 -0
- package/src/protocols/rdp/display.ts +750 -0
- package/src/protocols/rdp/gcc.ts +162 -0
- package/src/protocols/rdp/keymap.ts +68 -0
- package/src/protocols/rdp/mcs.ts +283 -0
- package/src/protocols/rdp/ntlm.ts +380 -0
- package/src/protocols/rdp/pdu.ts +497 -0
- package/src/protocols/rdp/rail.ts +480 -0
- package/src/protocols/rdp/rle.ts +343 -0
- package/src/protocols/rdp/sec.ts +184 -0
- package/src/protocols/rdp/session.ts +393 -0
- package/src/protocols/rdp/tls.ts +452 -0
- package/src/protocols/rdp/vchannel.ts +89 -0
- package/src/protocols/rdp/x224.ts +139 -0
- package/src/protocols/ssh/kex.ts +227 -0
- package/src/protocols/ssh/messages.ts +54 -0
- package/src/protocols/ssh/session.ts +878 -0
- package/src/protocols/ssh/terminal.ts +242 -0
- package/src/protocols/ssh/transport.ts +732 -0
- package/src/protocols/ssh/wire.ts +220 -0
- package/src/protocols/types.ts +146 -0
- package/src/protocols/vnc/input.ts +13 -0
- package/src/protocols/vnc/novnc.d.ts +75 -0
- package/src/protocols/vnc/session.ts +367 -0
- package/src/shared/connection.ts +206 -0
- package/src/shared/device.ts +28 -0
- package/src/shared/hosts.ts +240 -0
- package/src/shared/icons.ts +49 -0
- package/src/shared/protocol.ts +55 -0
- package/src/shared/signals.ts +67 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Crypto primitives for the RDP Standard Security layer (MS-RDPBCGR 5.3).
|
|
3
|
+
//
|
|
4
|
+
// RDP's own encryption layer predates anything WebCrypto exposes: it needs
|
|
5
|
+
// synchronous MD5 and SHA-1 for a per-PDU MAC, RC4 stream state that persists
|
|
6
|
+
// across PDUs, and textbook RSA against the key the server hands out in its
|
|
7
|
+
// certificate. All of it is small, so it lives here rather than as a
|
|
8
|
+
// dependency, and every function is deliberately synchronous -- the packet path
|
|
9
|
+
// runs inside WebSocket message handlers.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
// --- MD5 -------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
const MD5_SHIFTS = [
|
|
15
|
+
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
|
|
16
|
+
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
|
|
17
|
+
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
|
|
18
|
+
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// K[i] = floor(abs(sin(i + 1)) * 2^32) -- generated so the table cannot be mistyped.
|
|
22
|
+
const MD5_K = new Uint32Array(
|
|
23
|
+
Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 4294967296))
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function rotl32(value: Uint8Array, count: number) {
|
|
27
|
+
return (value << count) | (value >>> (32 - count));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function md5(message: Uint8Array) {
|
|
31
|
+
const bitLength = message.length * 8;
|
|
32
|
+
const blockCount = Math.floor((message.length + 8) / 64) + 1;
|
|
33
|
+
const padded = new Uint8Array(blockCount * 64);
|
|
34
|
+
padded.set(message);
|
|
35
|
+
padded[message.length] = 0x80;
|
|
36
|
+
|
|
37
|
+
// Length is appended as a 64-bit little-endian bit count.
|
|
38
|
+
const tail = new DataView(padded.buffer, padded.byteLength - 8, 8);
|
|
39
|
+
tail.setUint32(0, bitLength >>> 0, true);
|
|
40
|
+
tail.setUint32(4, Math.floor(bitLength / 4294967296), true);
|
|
41
|
+
|
|
42
|
+
let a0 = 0x67452301, b0 = 0xefcdab89, c0 = 0x98badcfe, d0 = 0x10325476;
|
|
43
|
+
const words = new Uint32Array(16);
|
|
44
|
+
const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
|
|
45
|
+
|
|
46
|
+
for (let block = 0; block < blockCount; block++) {
|
|
47
|
+
for (let i = 0; i < 16; i++) words[i] = view.getUint32(block * 64 + i * 4, true);
|
|
48
|
+
|
|
49
|
+
let a = a0, b = b0, c = c0, d = d0;
|
|
50
|
+
for (let i = 0; i < 64; i++) {
|
|
51
|
+
let f;
|
|
52
|
+
let g;
|
|
53
|
+
if (i < 16) {
|
|
54
|
+
f = (b & c) | (~b & d);
|
|
55
|
+
g = i;
|
|
56
|
+
} else if (i < 32) {
|
|
57
|
+
f = (d & b) | (~d & c);
|
|
58
|
+
g = (5 * i + 1) % 16;
|
|
59
|
+
} else if (i < 48) {
|
|
60
|
+
f = b ^ c ^ d;
|
|
61
|
+
g = (3 * i + 5) % 16;
|
|
62
|
+
} else {
|
|
63
|
+
f = c ^ (b | ~d);
|
|
64
|
+
g = (7 * i) % 16;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const temp = d;
|
|
68
|
+
d = c;
|
|
69
|
+
c = b;
|
|
70
|
+
const sum = (a + f + MD5_K[i] + words[g]) | 0;
|
|
71
|
+
b = (b + rotl32(sum, MD5_SHIFTS[i])) | 0;
|
|
72
|
+
a = temp;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
a0 = (a0 + a) | 0;
|
|
76
|
+
b0 = (b0 + b) | 0;
|
|
77
|
+
c0 = (c0 + c) | 0;
|
|
78
|
+
d0 = (d0 + d) | 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const digest = new Uint8Array(16);
|
|
82
|
+
const out = new DataView(digest.buffer);
|
|
83
|
+
out.setUint32(0, a0 >>> 0, true);
|
|
84
|
+
out.setUint32(4, b0 >>> 0, true);
|
|
85
|
+
out.setUint32(8, c0 >>> 0, true);
|
|
86
|
+
out.setUint32(12, d0 >>> 0, true);
|
|
87
|
+
return digest;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// --- SHA-1 -----------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
export function sha1(message: Uint8Array) {
|
|
93
|
+
const bitLength = message.length * 8;
|
|
94
|
+
const blockCount = Math.floor((message.length + 8) / 64) + 1;
|
|
95
|
+
const padded = new Uint8Array(blockCount * 64);
|
|
96
|
+
padded.set(message);
|
|
97
|
+
padded[message.length] = 0x80;
|
|
98
|
+
|
|
99
|
+
const tail = new DataView(padded.buffer, padded.byteLength - 8, 8);
|
|
100
|
+
tail.setUint32(0, Math.floor(bitLength / 4294967296));
|
|
101
|
+
tail.setUint32(4, bitLength >>> 0);
|
|
102
|
+
|
|
103
|
+
let h0 = 0x67452301, h1 = 0xefcdab89, h2 = 0x98badcfe, h3 = 0x10325476, h4 = 0xc3d2e1f0;
|
|
104
|
+
const words = new Uint32Array(80);
|
|
105
|
+
const view = new DataView(padded.buffer, padded.byteOffset, padded.byteLength);
|
|
106
|
+
|
|
107
|
+
for (let block = 0; block < blockCount; block++) {
|
|
108
|
+
for (let i = 0; i < 16; i++) words[i] = view.getUint32(block * 64 + i * 4);
|
|
109
|
+
for (let i = 16; i < 80; i++) {
|
|
110
|
+
words[i] = rotl32(words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16], 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let a = h0, b = h1, c = h2, d = h3, e = h4;
|
|
114
|
+
for (let i = 0; i < 80; i++) {
|
|
115
|
+
let f;
|
|
116
|
+
let k;
|
|
117
|
+
if (i < 20) {
|
|
118
|
+
f = (b & c) | (~b & d);
|
|
119
|
+
k = 0x5a827999;
|
|
120
|
+
} else if (i < 40) {
|
|
121
|
+
f = b ^ c ^ d;
|
|
122
|
+
k = 0x6ed9eba1;
|
|
123
|
+
} else if (i < 60) {
|
|
124
|
+
f = (b & c) | (b & d) | (c & d);
|
|
125
|
+
k = 0x8f1bbcdc;
|
|
126
|
+
} else {
|
|
127
|
+
f = b ^ c ^ d;
|
|
128
|
+
k = 0xca62c1d6;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const temp = (rotl32(a, 5) + f + e + k + words[i]) | 0;
|
|
132
|
+
e = d;
|
|
133
|
+
d = c;
|
|
134
|
+
c = rotl32(b, 30);
|
|
135
|
+
b = a;
|
|
136
|
+
a = temp;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
h0 = (h0 + a) | 0;
|
|
140
|
+
h1 = (h1 + b) | 0;
|
|
141
|
+
h2 = (h2 + c) | 0;
|
|
142
|
+
h3 = (h3 + d) | 0;
|
|
143
|
+
h4 = (h4 + e) | 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const digest = new Uint8Array(20);
|
|
147
|
+
const out = new DataView(digest.buffer);
|
|
148
|
+
[h0, h1, h2, h3, h4].forEach((word, i) => out.setUint32(i * 4, word >>> 0));
|
|
149
|
+
return digest;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// --- RC4 -------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/** RC4 keystream position. RDP re-keys every 4096 PDUs, so this is mutable. */
|
|
155
|
+
export interface Rc4State {
|
|
156
|
+
s: Uint8Array;
|
|
157
|
+
i: number;
|
|
158
|
+
j: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** The five values MS-RDPBCGR 5.3.5 derives from the two randoms. */
|
|
162
|
+
export interface SessionKeys {
|
|
163
|
+
macKey: Uint8Array;
|
|
164
|
+
keyLength: number;
|
|
165
|
+
is40Bit: boolean;
|
|
166
|
+
initialDecryptKey: Uint8Array;
|
|
167
|
+
initialEncryptKey: Uint8Array;
|
|
168
|
+
decryptKey: Uint8Array;
|
|
169
|
+
encryptKey: Uint8Array;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function rc4Init(key: Uint8Array): Rc4State {
|
|
173
|
+
const s = new Uint8Array(256);
|
|
174
|
+
for (let i = 0; i < 256; i++) s[i] = i;
|
|
175
|
+
|
|
176
|
+
let j = 0;
|
|
177
|
+
for (let i = 0; i < 256; i++) {
|
|
178
|
+
j = (j + (s[i] ?? 0) + (key[i % key.length] ?? 0)) & 0xff;
|
|
179
|
+
const t = s[i] ?? 0;
|
|
180
|
+
s[i] = s[j] ?? 0;
|
|
181
|
+
s[j] = t;
|
|
182
|
+
}
|
|
183
|
+
return { s, i: 0, j: 0 };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** RC4 is symmetric, so this both encrypts and decrypts. State advances. */
|
|
187
|
+
export function rc4Crypt(state, data: Uint8Array) {
|
|
188
|
+
const out = new Uint8Array(data.length);
|
|
189
|
+
const { s } = state;
|
|
190
|
+
let { i, j } = state;
|
|
191
|
+
|
|
192
|
+
for (let n = 0; n < data.length; n++) {
|
|
193
|
+
i = (i + 1) & 0xff;
|
|
194
|
+
j = (j + s[i]) & 0xff;
|
|
195
|
+
const t = s[i];
|
|
196
|
+
s[i] = s[j];
|
|
197
|
+
s[j] = t;
|
|
198
|
+
out[n] = data[n] ^ s[(s[i] + s[j]) & 0xff];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
state.i = i;
|
|
202
|
+
state.j = j;
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --- RSA -------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
function bigIntFromLe(bytes: Uint8Array) {
|
|
209
|
+
let value = 0n;
|
|
210
|
+
for (let i = bytes.length - 1; i >= 0; i--) value = (value << 8n) | BigInt(bytes[i]);
|
|
211
|
+
return value;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function bigIntToLe(value: Uint8Array, length: number) {
|
|
215
|
+
const out = new Uint8Array(length);
|
|
216
|
+
let remaining = value;
|
|
217
|
+
for (let i = 0; i < length; i++) {
|
|
218
|
+
out[i] = Number(remaining & 0xffn);
|
|
219
|
+
remaining >>= 8n;
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function modPow(base: bigint, exponent: bigint, modulus: bigint) {
|
|
225
|
+
let result = 1n;
|
|
226
|
+
let b = base % modulus;
|
|
227
|
+
let e = exponent;
|
|
228
|
+
while (e > 0n) {
|
|
229
|
+
if (e & 1n) result = (result * b) % modulus;
|
|
230
|
+
b = (b * b) % modulus;
|
|
231
|
+
e >>= 1n;
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Textbook RSA over little-endian buffers, which is what RDP's Security
|
|
238
|
+
* Exchange PDU carries: the client random is raised to the server's public
|
|
239
|
+
* exponent with no padding scheme (MS-RDPBCGR 5.3.4.1).
|
|
240
|
+
*/
|
|
241
|
+
export function rsaEncrypt(dataLe: Uint8Array, modulusLe: Uint8Array, exponentLe: Uint8Array) {
|
|
242
|
+
const modulus = bigIntFromLe(modulusLe);
|
|
243
|
+
const exponent = bigIntFromLe(exponentLe);
|
|
244
|
+
const value = bigIntFromLe(dataLe);
|
|
245
|
+
if (value >= modulus) throw new Error('RDP: client random is larger than the server RSA modulus');
|
|
246
|
+
return bigIntToLe(modPow(value, exponent, modulus), modulusLe.length);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* RSAES-PKCS1-v1_5 encryption over big-endian buffers, which is what TLS uses
|
|
251
|
+
* for an RSA key exchange. WebCrypto only offers OAEP, so the padding is built
|
|
252
|
+
* here and the modular exponentiation reuses the same BigInt path as RDP.
|
|
253
|
+
*/
|
|
254
|
+
export function rsaEncryptPkcs1(message: Uint8Array, modulusBe: Uint8Array, exponentBe: Uint8Array) {
|
|
255
|
+
const bigIntFromBe = (bytes: Uint8Array) => {
|
|
256
|
+
let value = 0n;
|
|
257
|
+
for (const byte of bytes) value = (value << 8n) | BigInt(byte);
|
|
258
|
+
return value;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const modulus = bigIntFromBe(modulusBe);
|
|
262
|
+
const exponent = bigIntFromBe(exponentBe);
|
|
263
|
+
const keyLength = modulusBe.length;
|
|
264
|
+
if (message.length > keyLength - 11) {
|
|
265
|
+
throw new Error('TLS: RSA message is too long for this key');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// EM = 0x00 || 0x02 || non-zero padding || 0x00 || message
|
|
269
|
+
const block = new Uint8Array(keyLength);
|
|
270
|
+
block[0] = 0x00;
|
|
271
|
+
block[1] = 0x02;
|
|
272
|
+
const paddingLength = keyLength - message.length - 3;
|
|
273
|
+
const padding = new Uint8Array(paddingLength);
|
|
274
|
+
crypto.getRandomValues(padding);
|
|
275
|
+
for (let i = 0; i < paddingLength; i++) {
|
|
276
|
+
// A padding byte of zero would be read as the separator.
|
|
277
|
+
while (padding[i] === 0) padding[i] = Math.max(1, crypto.getRandomValues(new Uint8Array(1))[0]);
|
|
278
|
+
block[2 + i] = padding[i];
|
|
279
|
+
}
|
|
280
|
+
block[2 + paddingLength] = 0x00;
|
|
281
|
+
block.set(message, 3 + paddingLength);
|
|
282
|
+
|
|
283
|
+
const cipher = modPow(bigIntFromBe(block), exponent, modulus);
|
|
284
|
+
const out = new Uint8Array(keyLength);
|
|
285
|
+
let remaining = cipher;
|
|
286
|
+
for (let i = keyLength - 1; i >= 0; i--) {
|
|
287
|
+
out[i] = Number(remaining & 0xffn);
|
|
288
|
+
remaining >>= 8n;
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- RDP key derivation (MS-RDPBCGR 5.3.5) ---------------------------------
|
|
294
|
+
|
|
295
|
+
const PAD1 = new Uint8Array(40).fill(0x36);
|
|
296
|
+
const PAD2 = new Uint8Array(48).fill(0x5c);
|
|
297
|
+
|
|
298
|
+
export const ENCRYPTION_METHOD = {
|
|
299
|
+
NONE: 0x00000000,
|
|
300
|
+
BIT_40: 0x00000001,
|
|
301
|
+
BIT_128: 0x00000002,
|
|
302
|
+
BIT_56: 0x00000008,
|
|
303
|
+
FIPS: 0x00000010,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
function concat(...parts) {
|
|
307
|
+
const total = parts.reduce((sum, part) => sum + part.length, 0);
|
|
308
|
+
const out = new Uint8Array(total);
|
|
309
|
+
let offset = 0;
|
|
310
|
+
for (const part of parts) {
|
|
311
|
+
out.set(part, offset);
|
|
312
|
+
offset += part.length;
|
|
313
|
+
}
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** SaltedHash(S, I) = MD5(S + SHA1(I + S + clientRandom + serverRandom)) */
|
|
318
|
+
function saltedHash(secret: Uint8Array, salt: Uint8Array, clientRandom: Uint8Array, serverRandom: Uint8Array) {
|
|
319
|
+
return md5(concat(secret, sha1(concat(salt, secret, clientRandom, serverRandom))));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** FinalHash(K) = MD5(K + clientRandom + serverRandom) */
|
|
323
|
+
function finalHash(key: Uint8Array, clientRandom: Uint8Array, serverRandom: Uint8Array) {
|
|
324
|
+
return md5(concat(key, clientRandom, serverRandom));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const SALT_A = new Uint8Array([0x41]);
|
|
328
|
+
const SALT_BB = new Uint8Array([0x42, 0x42]);
|
|
329
|
+
const SALT_CCC = new Uint8Array([0x43, 0x43, 0x43]);
|
|
330
|
+
const SALT_X = new Uint8Array([0x58]);
|
|
331
|
+
const SALT_YY = new Uint8Array([0x59, 0x59]);
|
|
332
|
+
const SALT_ZZZ = new Uint8Array([0x5a, 0x5a, 0x5a]);
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* A 40-bit or 56-bit session key is a 64-bit key with known leading bytes;
|
|
336
|
+
* only the remaining entropy is secret (MS-RDPBCGR 5.3.5.1).
|
|
337
|
+
*/
|
|
338
|
+
function reduceKey(key: Uint8Array, keyLength, is40Bit) {
|
|
339
|
+
if (keyLength !== 8) return key.slice(0, keyLength);
|
|
340
|
+
|
|
341
|
+
const reduced = key.slice(0, 8);
|
|
342
|
+
reduced[0] = 0xd1;
|
|
343
|
+
if (is40Bit) {
|
|
344
|
+
reduced[1] = 0x26;
|
|
345
|
+
reduced[2] = 0x9e;
|
|
346
|
+
}
|
|
347
|
+
return reduced;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function keyLengthForMethod(encryptionMethod: number) {
|
|
351
|
+
if (encryptionMethod === ENCRYPTION_METHOD.BIT_40) return 8;
|
|
352
|
+
if (encryptionMethod === ENCRYPTION_METHOD.BIT_56) return 8;
|
|
353
|
+
return 16;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Derive the MAC key and both directional RC4 keys from the two random values
|
|
358
|
+
* exchanged during connection.
|
|
359
|
+
*/
|
|
360
|
+
export function deriveSessionKeys(clientRandom: Uint8Array, serverRandom: Uint8Array, encryptionMethod: number) {
|
|
361
|
+
const preMasterSecret = concat(clientRandom.subarray(0, 24), serverRandom.subarray(0, 24));
|
|
362
|
+
|
|
363
|
+
const masterSecret = concat(
|
|
364
|
+
saltedHash(preMasterSecret, SALT_A, clientRandom, serverRandom),
|
|
365
|
+
saltedHash(preMasterSecret, SALT_BB, clientRandom, serverRandom),
|
|
366
|
+
saltedHash(preMasterSecret, SALT_CCC, clientRandom, serverRandom)
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const sessionKeyBlob = concat(
|
|
370
|
+
saltedHash(masterSecret, SALT_X, clientRandom, serverRandom),
|
|
371
|
+
saltedHash(masterSecret, SALT_YY, clientRandom, serverRandom),
|
|
372
|
+
saltedHash(masterSecret, SALT_ZZZ, clientRandom, serverRandom)
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const keyLength = keyLengthForMethod(encryptionMethod);
|
|
376
|
+
const is40Bit = encryptionMethod === ENCRYPTION_METHOD.BIT_40;
|
|
377
|
+
|
|
378
|
+
const applyReduction = (key: Uint8Array) => reduceKey(key, keyLength, is40Bit);
|
|
379
|
+
|
|
380
|
+
const macKey = applyReduction(sessionKeyBlob.subarray(0, 16));
|
|
381
|
+
const decryptKey = applyReduction(finalHash(sessionKeyBlob.subarray(16, 32), clientRandom, serverRandom));
|
|
382
|
+
const encryptKey = applyReduction(finalHash(sessionKeyBlob.subarray(32, 48), clientRandom, serverRandom));
|
|
383
|
+
|
|
384
|
+
return {
|
|
385
|
+
macKey,
|
|
386
|
+
keyLength,
|
|
387
|
+
is40Bit,
|
|
388
|
+
// Session-key updates always restart from these initial values.
|
|
389
|
+
initialDecryptKey: decryptKey.slice(),
|
|
390
|
+
initialEncryptKey: encryptKey.slice(),
|
|
391
|
+
decryptKey,
|
|
392
|
+
encryptKey,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* MACSignature = First64Bits(MD5(macKey + Pad2 + SHA1(macKey + Pad1 + len + data)))
|
|
398
|
+
*/
|
|
399
|
+
export function macSignature(macKey, data: Uint8Array) {
|
|
400
|
+
const lengthField = new Uint8Array(4);
|
|
401
|
+
new DataView(lengthField.buffer).setUint32(0, data.length, true);
|
|
402
|
+
const inner = sha1(concat(macKey, PAD1, lengthField, data));
|
|
403
|
+
return md5(concat(macKey, PAD2, inner)).subarray(0, 8);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Every 4096 PDUs the RC4 key is refreshed from the initial key
|
|
408
|
+
* (MS-RDPBCGR 5.3.7.1). The refreshed key is salted with itself.
|
|
409
|
+
*/
|
|
410
|
+
export function updateSessionKey(initialKey, currentKey, keyLength, is40Bit) {
|
|
411
|
+
const inner = sha1(concat(initialKey, PAD1, currentKey));
|
|
412
|
+
const nextKey = md5(concat(initialKey, PAD2, inner)).slice(0, keyLength);
|
|
413
|
+
|
|
414
|
+
const salted = rc4Crypt(rc4Init(nextKey), nextKey);
|
|
415
|
+
return reduceKey(salted, keyLength, is40Bit);
|
|
416
|
+
}
|