@electerm/ssh2 0.8.11 → 1.5.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.
@@ -0,0 +1,1831 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ createDiffieHellman,
5
+ createDiffieHellmanGroup,
6
+ createECDH,
7
+ createHash,
8
+ createPublicKey,
9
+ diffieHellman,
10
+ generateKeyPairSync,
11
+ randomFillSync,
12
+ } = require('crypto');
13
+
14
+ const { Ber } = require('asn1');
15
+
16
+ const {
17
+ COMPAT,
18
+ curve25519Supported,
19
+ DEFAULT_KEX,
20
+ DEFAULT_SERVER_HOST_KEY,
21
+ DEFAULT_CIPHER,
22
+ DEFAULT_MAC,
23
+ DEFAULT_COMPRESSION,
24
+ DISCONNECT_REASON,
25
+ MESSAGE,
26
+ } = require('./constants.js');
27
+ const {
28
+ CIPHER_INFO,
29
+ createCipher,
30
+ createDecipher,
31
+ MAC_INFO,
32
+ } = require('./crypto.js');
33
+ const { parseDERKey } = require('./keyParser.js');
34
+ const {
35
+ bufferFill,
36
+ bufferParser,
37
+ convertSignature,
38
+ doFatalError,
39
+ FastBuffer,
40
+ sigSSHToASN1,
41
+ writeUInt32BE,
42
+ } = require('./utils.js');
43
+ const {
44
+ PacketReader,
45
+ PacketWriter,
46
+ ZlibPacketReader,
47
+ ZlibPacketWriter,
48
+ } = require('./zlib.js');
49
+
50
+ let MESSAGE_HANDLERS;
51
+
52
+ const GEX_MIN_BITS = 2048; // RFC 8270
53
+ const GEX_MAX_BITS = 8192; // RFC 8270
54
+
55
+ const EMPTY_BUFFER = Buffer.alloc(0);
56
+
57
+ // Client/Server
58
+ function kexinit(self) {
59
+ /*
60
+ byte SSH_MSG_KEXINIT
61
+ byte[16] cookie (random bytes)
62
+ name-list kex_algorithms
63
+ name-list server_host_key_algorithms
64
+ name-list encryption_algorithms_client_to_server
65
+ name-list encryption_algorithms_server_to_client
66
+ name-list mac_algorithms_client_to_server
67
+ name-list mac_algorithms_server_to_client
68
+ name-list compression_algorithms_client_to_server
69
+ name-list compression_algorithms_server_to_client
70
+ name-list languages_client_to_server
71
+ name-list languages_server_to_client
72
+ boolean first_kex_packet_follows
73
+ uint32 0 (reserved for future extension)
74
+ */
75
+
76
+ let payload;
77
+ if (self._compatFlags & COMPAT.BAD_DHGEX) {
78
+ const entry = self._offer.lists.kex;
79
+ let kex = entry.array;
80
+ let found = false;
81
+ for (let i = 0; i < kex.length; ++i) {
82
+ if (kex[i].includes('group-exchange')) {
83
+ if (!found) {
84
+ found = true;
85
+ // Copy array lazily
86
+ kex = kex.slice();
87
+ }
88
+ kex.splice(i--, 1);
89
+ }
90
+ }
91
+ if (found) {
92
+ let len = 1 + 16 + self._offer.totalSize + 1 + 4;
93
+ const newKexBuf = Buffer.from(kex.join(','));
94
+ len -= (entry.buffer.length - newKexBuf.length);
95
+
96
+ const all = self._offer.lists.all;
97
+ const rest = new Uint8Array(
98
+ all.buffer,
99
+ all.byteOffset + 4 + entry.buffer.length,
100
+ all.length - (4 + entry.buffer.length)
101
+ );
102
+
103
+ payload = Buffer.allocUnsafe(len);
104
+ writeUInt32BE(payload, newKexBuf.length, 17);
105
+ payload.set(newKexBuf, 17 + 4);
106
+ payload.set(rest, 17 + 4 + newKexBuf.length);
107
+ }
108
+ }
109
+
110
+ if (payload === undefined) {
111
+ payload = Buffer.allocUnsafe(1 + 16 + self._offer.totalSize + 1 + 4);
112
+ self._offer.copyAllTo(payload, 17);
113
+ }
114
+
115
+ self._debug && self._debug('Outbound: Sending KEXINIT');
116
+
117
+ payload[0] = MESSAGE.KEXINIT;
118
+ randomFillSync(payload, 1, 16);
119
+
120
+ // Zero-fill first_kex_packet_follows and reserved bytes
121
+ bufferFill(payload, 0, payload.length - 5);
122
+
123
+ self._kexinit = payload;
124
+
125
+ // Needed to correct the starting position in allocated "packets" when packets
126
+ // will be buffered due to active key exchange
127
+ self._packetRW.write.allocStart = 0;
128
+
129
+ // TODO: only create single buffer and set _kexinit as slice of packet instead
130
+ {
131
+ const p = self._packetRW.write.allocStartKEX;
132
+ const packet = self._packetRW.write.alloc(payload.length, true);
133
+ packet.set(payload, p);
134
+ self._cipher.encrypt(self._packetRW.write.finalize(packet, true));
135
+ }
136
+ }
137
+
138
+ function handleKexInit(self, payload) {
139
+ /*
140
+ byte SSH_MSG_KEXINIT
141
+ byte[16] cookie (random bytes)
142
+ name-list kex_algorithms
143
+ name-list server_host_key_algorithms
144
+ name-list encryption_algorithms_client_to_server
145
+ name-list encryption_algorithms_server_to_client
146
+ name-list mac_algorithms_client_to_server
147
+ name-list mac_algorithms_server_to_client
148
+ name-list compression_algorithms_client_to_server
149
+ name-list compression_algorithms_server_to_client
150
+ name-list languages_client_to_server
151
+ name-list languages_server_to_client
152
+ boolean first_kex_packet_follows
153
+ uint32 0 (reserved for future extension)
154
+ */
155
+ const init = {
156
+ kex: undefined,
157
+ serverHostKey: undefined,
158
+ cs: {
159
+ cipher: undefined,
160
+ mac: undefined,
161
+ compress: undefined,
162
+ lang: undefined,
163
+ },
164
+ sc: {
165
+ cipher: undefined,
166
+ mac: undefined,
167
+ compress: undefined,
168
+ lang: undefined,
169
+ },
170
+ };
171
+
172
+ bufferParser.init(payload, 17);
173
+
174
+ if ((init.kex = bufferParser.readList()) === undefined
175
+ || (init.serverHostKey = bufferParser.readList()) === undefined
176
+ || (init.cs.cipher = bufferParser.readList()) === undefined
177
+ || (init.sc.cipher = bufferParser.readList()) === undefined
178
+ || (init.cs.mac = bufferParser.readList()) === undefined
179
+ || (init.sc.mac = bufferParser.readList()) === undefined
180
+ || (init.cs.compress = bufferParser.readList()) === undefined
181
+ || (init.sc.compress = bufferParser.readList()) === undefined
182
+ || (init.cs.lang = bufferParser.readList()) === undefined
183
+ || (init.sc.lang = bufferParser.readList()) === undefined) {
184
+ bufferParser.clear();
185
+ return doFatalError(
186
+ self,
187
+ 'Received malformed KEXINIT',
188
+ 'handshake',
189
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
190
+ );
191
+ }
192
+
193
+ const pos = bufferParser.pos();
194
+ const firstFollows = (pos < payload.length && payload[pos] === 1);
195
+ bufferParser.clear();
196
+
197
+ const local = self._offer;
198
+ const remote = init;
199
+
200
+ let localKex = local.lists.kex.array;
201
+ if (self._compatFlags & COMPAT.BAD_DHGEX) {
202
+ let found = false;
203
+ for (let i = 0; i < localKex.length; ++i) {
204
+ if (localKex[i].indexOf('group-exchange') !== -1) {
205
+ if (!found) {
206
+ found = true;
207
+ // Copy array lazily
208
+ localKex = localKex.slice();
209
+ }
210
+ localKex.splice(i--, 1);
211
+ }
212
+ }
213
+ }
214
+
215
+ let clientList;
216
+ let serverList;
217
+ let i;
218
+ const debug = self._debug;
219
+
220
+ debug && debug('Inbound: Handshake in progress');
221
+
222
+ // Key exchange method =======================================================
223
+ debug && debug(`Handshake: (local) KEX method: ${localKex}`);
224
+ debug && debug(`Handshake: (remote) KEX method: ${remote.kex}`);
225
+ if (self._server) {
226
+ serverList = localKex;
227
+ clientList = remote.kex;
228
+ } else {
229
+ serverList = remote.kex;
230
+ clientList = localKex;
231
+ }
232
+ // Check for agreeable key exchange algorithm
233
+ for (i = 0;
234
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
235
+ ++i);
236
+ if (i === clientList.length) {
237
+ // No suitable match found!
238
+ debug && debug('Handshake: No matching key exchange algorithm');
239
+ return doFatalError(
240
+ self,
241
+ 'Handshake failed: no matching key exchange algorithm',
242
+ 'handshake',
243
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
244
+ );
245
+ }
246
+ init.kex = clientList[i];
247
+ debug && debug(`Handshake: KEX algorithm: ${clientList[i]}`);
248
+ if (firstFollows && (!remote.kex.length || clientList[i] !== remote.kex[0])) {
249
+ // Ignore next inbound packet, it was a wrong first guess at KEX algorithm
250
+ self._skipNextInboundPacket = true;
251
+ }
252
+
253
+
254
+ // Server host key format ====================================================
255
+ const localSrvHostKey = local.lists.serverHostKey.array;
256
+ debug && debug(`Handshake: (local) Host key format: ${localSrvHostKey}`);
257
+ debug && debug(
258
+ `Handshake: (remote) Host key format: ${remote.serverHostKey}`
259
+ );
260
+ if (self._server) {
261
+ serverList = localSrvHostKey;
262
+ clientList = remote.serverHostKey;
263
+ } else {
264
+ serverList = remote.serverHostKey;
265
+ clientList = localSrvHostKey;
266
+ }
267
+ // Check for agreeable server host key format
268
+ for (i = 0;
269
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
270
+ ++i);
271
+ if (i === clientList.length) {
272
+ // No suitable match found!
273
+ debug && debug('Handshake: No matching host key format');
274
+ return doFatalError(
275
+ self,
276
+ 'Handshake failed: no matching host key format',
277
+ 'handshake',
278
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
279
+ );
280
+ }
281
+ init.serverHostKey = clientList[i];
282
+ debug && debug(`Handshake: Host key format: ${clientList[i]}`);
283
+
284
+
285
+ // Client->Server cipher =====================================================
286
+ const localCSCipher = local.lists.cs.cipher.array;
287
+ debug && debug(`Handshake: (local) C->S cipher: ${localCSCipher}`);
288
+ debug && debug(`Handshake: (remote) C->S cipher: ${remote.cs.cipher}`);
289
+ if (self._server) {
290
+ serverList = localCSCipher;
291
+ clientList = remote.cs.cipher;
292
+ } else {
293
+ serverList = remote.cs.cipher;
294
+ clientList = localCSCipher;
295
+ }
296
+ // Check for agreeable client->server cipher
297
+ for (i = 0;
298
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
299
+ ++i);
300
+ if (i === clientList.length) {
301
+ // No suitable match found!
302
+ debug && debug('Handshake: No matching C->S cipher');
303
+ return doFatalError(
304
+ self,
305
+ 'Handshake failed: no matching C->S cipher',
306
+ 'handshake',
307
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
308
+ );
309
+ }
310
+ init.cs.cipher = clientList[i];
311
+ debug && debug(`Handshake: C->S Cipher: ${clientList[i]}`);
312
+
313
+
314
+ // Server->Client cipher =====================================================
315
+ const localSCCipher = local.lists.sc.cipher.array;
316
+ debug && debug(`Handshake: (local) S->C cipher: ${localSCCipher}`);
317
+ debug && debug(`Handshake: (remote) S->C cipher: ${remote.sc.cipher}`);
318
+ if (self._server) {
319
+ serverList = localSCCipher;
320
+ clientList = remote.sc.cipher;
321
+ } else {
322
+ serverList = remote.sc.cipher;
323
+ clientList = localSCCipher;
324
+ }
325
+ // Check for agreeable server->client cipher
326
+ for (i = 0;
327
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
328
+ ++i);
329
+ if (i === clientList.length) {
330
+ // No suitable match found!
331
+ debug && debug('Handshake: No matching S->C cipher');
332
+ return doFatalError(
333
+ self,
334
+ 'Handshake failed: no matching S->C cipher',
335
+ 'handshake',
336
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
337
+ );
338
+ }
339
+ init.sc.cipher = clientList[i];
340
+ debug && debug(`Handshake: S->C cipher: ${clientList[i]}`);
341
+
342
+
343
+ // Client->Server MAC ========================================================
344
+ const localCSMAC = local.lists.cs.mac.array;
345
+ debug && debug(`Handshake: (local) C->S MAC: ${localCSMAC}`);
346
+ debug && debug(`Handshake: (remote) C->S MAC: ${remote.cs.mac}`);
347
+ if (CIPHER_INFO[init.cs.cipher].authLen > 0) {
348
+ init.cs.mac = '';
349
+ debug && debug('Handshake: C->S MAC: <implicit>');
350
+ } else {
351
+ if (self._server) {
352
+ serverList = localCSMAC;
353
+ clientList = remote.cs.mac;
354
+ } else {
355
+ serverList = remote.cs.mac;
356
+ clientList = localCSMAC;
357
+ }
358
+ // Check for agreeable client->server hmac algorithm
359
+ for (i = 0;
360
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
361
+ ++i);
362
+ if (i === clientList.length) {
363
+ // No suitable match found!
364
+ debug && debug('Handshake: No matching C->S MAC');
365
+ return doFatalError(
366
+ self,
367
+ 'Handshake failed: no matching C->S MAC',
368
+ 'handshake',
369
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
370
+ );
371
+ }
372
+ init.cs.mac = clientList[i];
373
+ debug && debug(`Handshake: C->S MAC: ${clientList[i]}`);
374
+ }
375
+
376
+
377
+ // Server->Client MAC ========================================================
378
+ const localSCMAC = local.lists.sc.mac.array;
379
+ debug && debug(`Handshake: (local) S->C MAC: ${localSCMAC}`);
380
+ debug && debug(`Handshake: (remote) S->C MAC: ${remote.sc.mac}`);
381
+ if (CIPHER_INFO[init.sc.cipher].authLen > 0) {
382
+ init.sc.mac = '';
383
+ debug && debug('Handshake: S->C MAC: <implicit>');
384
+ } else {
385
+ if (self._server) {
386
+ serverList = localSCMAC;
387
+ clientList = remote.sc.mac;
388
+ } else {
389
+ serverList = remote.sc.mac;
390
+ clientList = localSCMAC;
391
+ }
392
+ // Check for agreeable server->client hmac algorithm
393
+ for (i = 0;
394
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
395
+ ++i);
396
+ if (i === clientList.length) {
397
+ // No suitable match found!
398
+ debug && debug('Handshake: No matching S->C MAC');
399
+ return doFatalError(
400
+ self,
401
+ 'Handshake failed: no matching S->C MAC',
402
+ 'handshake',
403
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
404
+ );
405
+ }
406
+ init.sc.mac = clientList[i];
407
+ debug && debug(`Handshake: S->C MAC: ${clientList[i]}`);
408
+ }
409
+
410
+
411
+ // Client->Server compression ================================================
412
+ const localCSCompress = local.lists.cs.compress.array;
413
+ debug && debug(`Handshake: (local) C->S compression: ${localCSCompress}`);
414
+ debug && debug(`Handshake: (remote) C->S compression: ${remote.cs.compress}`);
415
+ if (self._server) {
416
+ serverList = localCSCompress;
417
+ clientList = remote.cs.compress;
418
+ } else {
419
+ serverList = remote.cs.compress;
420
+ clientList = localCSCompress;
421
+ }
422
+ // Check for agreeable client->server compression algorithm
423
+ for (i = 0;
424
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
425
+ ++i);
426
+ if (i === clientList.length) {
427
+ // No suitable match found!
428
+ debug && debug('Handshake: No matching C->S compression');
429
+ return doFatalError(
430
+ self,
431
+ 'Handshake failed: no matching C->S compression',
432
+ 'handshake',
433
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
434
+ );
435
+ }
436
+ init.cs.compress = clientList[i];
437
+ debug && debug(`Handshake: C->S compression: ${clientList[i]}`);
438
+
439
+
440
+ // Server->Client compression ================================================
441
+ const localSCCompress = local.lists.sc.compress.array;
442
+ debug && debug(`Handshake: (local) S->C compression: ${localSCCompress}`);
443
+ debug && debug(`Handshake: (remote) S->C compression: ${remote.sc.compress}`);
444
+ if (self._server) {
445
+ serverList = localSCCompress;
446
+ clientList = remote.sc.compress;
447
+ } else {
448
+ serverList = remote.sc.compress;
449
+ clientList = localSCCompress;
450
+ }
451
+ // Check for agreeable server->client compression algorithm
452
+ for (i = 0;
453
+ i < clientList.length && serverList.indexOf(clientList[i]) === -1;
454
+ ++i);
455
+ if (i === clientList.length) {
456
+ // No suitable match found!
457
+ debug && debug('Handshake: No matching S->C compression');
458
+ return doFatalError(
459
+ self,
460
+ 'Handshake failed: no matching S->C compression',
461
+ 'handshake',
462
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
463
+ );
464
+ }
465
+ init.sc.compress = clientList[i];
466
+ debug && debug(`Handshake: S->C compression: ${clientList[i]}`);
467
+
468
+ init.cs.lang = '';
469
+ init.sc.lang = '';
470
+
471
+ // XXX: hack -- find a better way to do this
472
+ if (self._kex) {
473
+ if (!self._kexinit) {
474
+ // We received a rekey request, but we haven't sent a KEXINIT in response
475
+ // yet
476
+ kexinit(self);
477
+ }
478
+ self._decipher._onPayload = onKEXPayload.bind(self, { firstPacket: false });
479
+ }
480
+
481
+ self._kex = createKeyExchange(init, self, payload);
482
+ self._kex.start();
483
+ }
484
+
485
+ const createKeyExchange = (() => {
486
+ function convertToMpint(buf) {
487
+ let idx = 0;
488
+ let length = buf.length;
489
+ while (buf[idx] === 0x00) {
490
+ ++idx;
491
+ --length;
492
+ }
493
+ let newBuf;
494
+ if (buf[idx] & 0x80) {
495
+ newBuf = Buffer.allocUnsafe(1 + length);
496
+ newBuf[0] = 0;
497
+ buf.copy(newBuf, 1, idx);
498
+ buf = newBuf;
499
+ } else if (length !== buf.length) {
500
+ newBuf = Buffer.allocUnsafe(length);
501
+ buf.copy(newBuf, 0, idx);
502
+ buf = newBuf;
503
+ }
504
+ return buf;
505
+ }
506
+
507
+ class KeyExchange {
508
+ constructor(negotiated, protocol, remoteKexinit) {
509
+ this._protocol = protocol;
510
+
511
+ this.sessionID = (protocol._kex ? protocol._kex.sessionID : undefined);
512
+ this.negotiated = negotiated;
513
+ this._step = 1;
514
+ this._public = null;
515
+ this._dh = null;
516
+ this._sentNEWKEYS = false;
517
+ this._receivedNEWKEYS = false;
518
+ this._finished = false;
519
+ this._hostVerified = false;
520
+
521
+ // Data needed for initializing cipher/decipher/etc.
522
+ this._kexinit = protocol._kexinit;
523
+ this._remoteKexinit = remoteKexinit;
524
+ this._identRaw = protocol._identRaw;
525
+ this._remoteIdentRaw = protocol._remoteIdentRaw;
526
+ this._hostKey = undefined;
527
+ this._dhData = undefined;
528
+ this._sig = undefined;
529
+ }
530
+ finish() {
531
+ if (this._finished)
532
+ return false;
533
+ this._finished = true;
534
+
535
+ const isServer = this._protocol._server;
536
+ const negotiated = this.negotiated;
537
+
538
+ const pubKey = this.convertPublicKey(this._dhData);
539
+ let secret = this.computeSecret(this._dhData);
540
+ if (secret instanceof Error) {
541
+ secret.message =
542
+ `Error while computing DH secret (${this.type}): ${secret.message}`;
543
+ secret.level = 'handshake';
544
+ return doFatalError(
545
+ this._protocol,
546
+ secret,
547
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
548
+ );
549
+ }
550
+
551
+ const hash = createHash(this.hashName);
552
+ // V_C
553
+ hashString(hash, (isServer ? this._remoteIdentRaw : this._identRaw));
554
+ // "V_S"
555
+ hashString(hash, (isServer ? this._identRaw : this._remoteIdentRaw));
556
+ // "I_C"
557
+ hashString(hash, (isServer ? this._remoteKexinit : this._kexinit));
558
+ // "I_S"
559
+ hashString(hash, (isServer ? this._kexinit : this._remoteKexinit));
560
+ // "K_S"
561
+ const serverPublicHostKey = (isServer
562
+ ? this._hostKey.getPublicSSH()
563
+ : this._hostKey);
564
+ hashString(hash, serverPublicHostKey);
565
+
566
+ if (this.type === 'groupex') {
567
+ // Group exchange-specific
568
+ const params = this.getDHParams();
569
+ const num = Buffer.allocUnsafe(4);
570
+ // min (uint32)
571
+ writeUInt32BE(num, this._minBits, 0);
572
+ hash.update(num);
573
+ // preferred (uint32)
574
+ writeUInt32BE(num, this._prefBits, 0);
575
+ hash.update(num);
576
+ // max (uint32)
577
+ writeUInt32BE(num, this._maxBits, 0);
578
+ hash.update(num);
579
+ // prime
580
+ hashString(hash, params.prime);
581
+ // generator
582
+ hashString(hash, params.generator);
583
+ }
584
+
585
+ // method-specific data sent by client
586
+ hashString(hash, (isServer ? pubKey : this.getPublicKey()));
587
+ // method-specific data sent by server
588
+ const serverPublicKey = (isServer ? this.getPublicKey() : pubKey);
589
+ hashString(hash, serverPublicKey);
590
+ // shared secret ("K")
591
+ hashString(hash, secret);
592
+
593
+ // "H"
594
+ const exchangeHash = hash.digest();
595
+
596
+ if (!isServer) {
597
+ bufferParser.init(this._sig, 0);
598
+ const sigType = bufferParser.readString(true);
599
+
600
+ if (!sigType) {
601
+ return doFatalError(
602
+ this._protocol,
603
+ 'Malformed packet while reading signature',
604
+ 'handshake',
605
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
606
+ );
607
+ }
608
+
609
+ if (sigType !== negotiated.serverHostKey) {
610
+ return doFatalError(
611
+ this._protocol,
612
+ `Wrong signature type: ${sigType}, `
613
+ + `expected: ${negotiated.serverHostKey}`,
614
+ 'handshake',
615
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
616
+ );
617
+ }
618
+
619
+ // "s"
620
+ let sigValue = bufferParser.readString();
621
+
622
+ bufferParser.clear();
623
+
624
+ if (sigValue === undefined) {
625
+ return doFatalError(
626
+ this._protocol,
627
+ 'Malformed packet while reading signature',
628
+ 'handshake',
629
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
630
+ );
631
+ }
632
+
633
+ if (!(sigValue = sigSSHToASN1(sigValue, sigType))) {
634
+ return doFatalError(
635
+ this._protocol,
636
+ 'Malformed signature',
637
+ 'handshake',
638
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
639
+ );
640
+ }
641
+
642
+ let parsedHostKey;
643
+ {
644
+ bufferParser.init(this._hostKey, 0);
645
+ const name = bufferParser.readString(true);
646
+ const hostKey = this._hostKey.slice(bufferParser.pos());
647
+ bufferParser.clear();
648
+ parsedHostKey = parseDERKey(hostKey, name);
649
+ if (parsedHostKey instanceof Error) {
650
+ parsedHostKey.level = 'handshake';
651
+ return doFatalError(
652
+ this._protocol,
653
+ parsedHostKey,
654
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
655
+ );
656
+ }
657
+ }
658
+
659
+ let hashAlgo;
660
+ // Check if we need to override the default hash algorithm
661
+ switch (this.negotiated.serverHostKey) {
662
+ case 'rsa-sha2-256': hashAlgo = 'sha256'; break;
663
+ case 'rsa-sha2-512': hashAlgo = 'sha512'; break;
664
+ }
665
+
666
+ this._protocol._debug
667
+ && this._protocol._debug('Verifying signature ...');
668
+
669
+ const verified = parsedHostKey.verify(exchangeHash, sigValue, hashAlgo);
670
+ if (verified !== true) {
671
+ if (verified instanceof Error) {
672
+ this._protocol._debug && this._protocol._debug(
673
+ `Signature verification failed: ${verified.stack}`
674
+ );
675
+ } else {
676
+ this._protocol._debug && this._protocol._debug(
677
+ 'Signature verification failed'
678
+ );
679
+ }
680
+ return doFatalError(
681
+ this._protocol,
682
+ 'Handshake failed: signature verification failed',
683
+ 'handshake',
684
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
685
+ );
686
+ }
687
+ this._protocol._debug && this._protocol._debug('Verified signature');
688
+ } else {
689
+ // Server
690
+
691
+ let hashAlgo;
692
+ // Check if we need to override the default hash algorithm
693
+ switch (this.negotiated.serverHostKey) {
694
+ case 'rsa-sha2-256': hashAlgo = 'sha256'; break;
695
+ case 'rsa-sha2-512': hashAlgo = 'sha512'; break;
696
+ }
697
+
698
+ this._protocol._debug && this._protocol._debug(
699
+ 'Generating signature ...'
700
+ );
701
+
702
+ let signature = this._hostKey.sign(exchangeHash, hashAlgo);
703
+ if (signature instanceof Error) {
704
+ return doFatalError(
705
+ this._protocol,
706
+ 'Handshake failed: signature generation failed for '
707
+ + `${this._hostKey.type} host key: ${signature.message}`,
708
+ 'handshake',
709
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
710
+ );
711
+ }
712
+
713
+ signature = convertSignature(signature, this._hostKey.type);
714
+ if (signature === false) {
715
+ return doFatalError(
716
+ this._protocol,
717
+ 'Handshake failed: signature conversion failed for '
718
+ + `${this._hostKey.type} host key`,
719
+ 'handshake',
720
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
721
+ );
722
+ }
723
+
724
+ // Send KEX reply
725
+ /*
726
+ byte SSH_MSG_KEXDH_REPLY
727
+ / SSH_MSG_KEX_DH_GEX_REPLY
728
+ / SSH_MSG_KEX_ECDH_REPLY
729
+ string server public host key and certificates (K_S)
730
+ string <method-specific data>
731
+ string signature of H
732
+ */
733
+ const sigType = this.negotiated.serverHostKey;
734
+ const sigTypeLen = Buffer.byteLength(sigType);
735
+ const sigLen = 4 + sigTypeLen + 4 + signature.length;
736
+ let p = this._protocol._packetRW.write.allocStartKEX;
737
+ const packet = this._protocol._packetRW.write.alloc(
738
+ 1
739
+ + 4 + serverPublicHostKey.length
740
+ + 4 + serverPublicKey.length
741
+ + 4 + sigLen,
742
+ true
743
+ );
744
+
745
+ packet[p] = MESSAGE.KEXDH_REPLY;
746
+
747
+ writeUInt32BE(packet, serverPublicHostKey.length, ++p);
748
+ packet.set(serverPublicHostKey, p += 4);
749
+
750
+ writeUInt32BE(packet,
751
+ serverPublicKey.length,
752
+ p += serverPublicHostKey.length);
753
+ packet.set(serverPublicKey, p += 4);
754
+
755
+ writeUInt32BE(packet, sigLen, p += serverPublicKey.length);
756
+
757
+ writeUInt32BE(packet, sigTypeLen, p += 4);
758
+ packet.utf8Write(sigType, p += 4, sigTypeLen);
759
+
760
+ writeUInt32BE(packet, signature.length, p += sigTypeLen);
761
+ packet.set(signature, p += 4);
762
+
763
+ if (this._protocol._debug) {
764
+ let type;
765
+ switch (this.type) {
766
+ case 'group':
767
+ type = 'KEXDH_REPLY';
768
+ break;
769
+ case 'groupex':
770
+ type = 'KEXDH_GEX_REPLY';
771
+ break;
772
+ default:
773
+ type = 'KEXECDH_REPLY';
774
+ }
775
+ this._protocol._debug(`Outbound: Sending ${type}`);
776
+ }
777
+ this._protocol._cipher.encrypt(
778
+ this._protocol._packetRW.write.finalize(packet, true)
779
+ );
780
+ }
781
+ trySendNEWKEYS(this);
782
+
783
+ const completeHandshake = () => {
784
+ if (!this.sessionID)
785
+ this.sessionID = exchangeHash;
786
+
787
+ {
788
+ const newSecret = Buffer.allocUnsafe(4 + secret.length);
789
+ writeUInt32BE(newSecret, secret.length, 0);
790
+ newSecret.set(secret, 4);
791
+ secret = newSecret;
792
+ }
793
+
794
+ // Initialize new ciphers, deciphers, etc.
795
+
796
+ const csCipherInfo = CIPHER_INFO[negotiated.cs.cipher];
797
+ const scCipherInfo = CIPHER_INFO[negotiated.sc.cipher];
798
+
799
+ const csIV = generateKEXVal(csCipherInfo.ivLen,
800
+ this.hashName,
801
+ secret,
802
+ exchangeHash,
803
+ this.sessionID,
804
+ 'A');
805
+ const scIV = generateKEXVal(scCipherInfo.ivLen,
806
+ this.hashName,
807
+ secret,
808
+ exchangeHash,
809
+ this.sessionID,
810
+ 'B');
811
+ const csKey = generateKEXVal(csCipherInfo.keyLen,
812
+ this.hashName,
813
+ secret,
814
+ exchangeHash,
815
+ this.sessionID,
816
+ 'C');
817
+ const scKey = generateKEXVal(scCipherInfo.keyLen,
818
+ this.hashName,
819
+ secret,
820
+ exchangeHash,
821
+ this.sessionID,
822
+ 'D');
823
+ let csMacInfo;
824
+ let csMacKey;
825
+ if (!csCipherInfo.authLen) {
826
+ csMacInfo = MAC_INFO[negotiated.cs.mac];
827
+ csMacKey = generateKEXVal(csMacInfo.len,
828
+ this.hashName,
829
+ secret,
830
+ exchangeHash,
831
+ this.sessionID,
832
+ 'E');
833
+ }
834
+ let scMacInfo;
835
+ let scMacKey;
836
+ if (!scCipherInfo.authLen) {
837
+ scMacInfo = MAC_INFO[negotiated.sc.mac];
838
+ scMacKey = generateKEXVal(scMacInfo.len,
839
+ this.hashName,
840
+ secret,
841
+ exchangeHash,
842
+ this.sessionID,
843
+ 'F');
844
+ }
845
+
846
+ const config = {
847
+ inbound: {
848
+ onPayload: this._protocol._onPayload,
849
+ seqno: this._protocol._decipher.inSeqno,
850
+ decipherInfo: (!isServer ? scCipherInfo : csCipherInfo),
851
+ decipherIV: (!isServer ? scIV : csIV),
852
+ decipherKey: (!isServer ? scKey : csKey),
853
+ macInfo: (!isServer ? scMacInfo : csMacInfo),
854
+ macKey: (!isServer ? scMacKey : csMacKey),
855
+ },
856
+ outbound: {
857
+ onWrite: this._protocol._onWrite,
858
+ seqno: this._protocol._cipher.outSeqno,
859
+ cipherInfo: (isServer ? scCipherInfo : csCipherInfo),
860
+ cipherIV: (isServer ? scIV : csIV),
861
+ cipherKey: (isServer ? scKey : csKey),
862
+ macInfo: (isServer ? scMacInfo : csMacInfo),
863
+ macKey: (isServer ? scMacKey : csMacKey),
864
+ },
865
+ };
866
+ this._protocol._cipher && this._protocol._cipher.free();
867
+ this._protocol._decipher && this._protocol._decipher.free();
868
+ this._protocol._cipher = createCipher(config);
869
+ this._protocol._decipher = createDecipher(config);
870
+
871
+ const rw = {
872
+ read: undefined,
873
+ write: undefined,
874
+ };
875
+ switch (negotiated.cs.compress) {
876
+ case 'zlib': // starts immediately
877
+ if (isServer)
878
+ rw.read = new ZlibPacketReader();
879
+ else
880
+ rw.write = new ZlibPacketWriter(this._protocol);
881
+ break;
882
+ case 'zlib@openssh.com':
883
+ // Starts after successful user authentication
884
+
885
+ if (this._protocol._authenticated) {
886
+ // If a rekey happens and this compression method is selected and
887
+ // we already authenticated successfully, we need to start
888
+ // immediately instead
889
+ if (isServer)
890
+ rw.read = new ZlibPacketReader();
891
+ else
892
+ rw.write = new ZlibPacketWriter(this._protocol);
893
+ break;
894
+ }
895
+ // FALLTHROUGH
896
+ default:
897
+ // none -- never any compression/decompression
898
+
899
+ if (isServer)
900
+ rw.read = new PacketReader();
901
+ else
902
+ rw.write = new PacketWriter(this._protocol);
903
+ }
904
+ switch (negotiated.sc.compress) {
905
+ case 'zlib': // starts immediately
906
+ if (isServer)
907
+ rw.write = new ZlibPacketWriter(this._protocol);
908
+ else
909
+ rw.read = new ZlibPacketReader();
910
+ break;
911
+ case 'zlib@openssh.com':
912
+ // Starts after successful user authentication
913
+
914
+ if (this._protocol._authenticated) {
915
+ // If a rekey happens and this compression method is selected and
916
+ // we already authenticated successfully, we need to start
917
+ // immediately instead
918
+ if (isServer)
919
+ rw.write = new ZlibPacketWriter(this._protocol);
920
+ else
921
+ rw.read = new ZlibPacketReader();
922
+ break;
923
+ }
924
+ // FALLTHROUGH
925
+ default:
926
+ // none -- never any compression/decompression
927
+
928
+ if (isServer)
929
+ rw.write = new PacketWriter(this._protocol);
930
+ else
931
+ rw.read = new PacketReader();
932
+ }
933
+ this._protocol._packetRW.read.cleanup();
934
+ this._protocol._packetRW.write.cleanup();
935
+ this._protocol._packetRW = rw;
936
+
937
+ // Cleanup/reset various state
938
+ this._public = null;
939
+ this._dh = null;
940
+ this._kexinit = this._protocol._kexinit = undefined;
941
+ this._remoteKexinit = undefined;
942
+ this._identRaw = undefined;
943
+ this._remoteIdentRaw = undefined;
944
+ this._hostKey = undefined;
945
+ this._dhData = undefined;
946
+ this._sig = undefined;
947
+
948
+ this._protocol._onHandshakeComplete(negotiated);
949
+
950
+ return false;
951
+ };
952
+ if (!isServer)
953
+ return completeHandshake();
954
+ this.finish = completeHandshake;
955
+ }
956
+
957
+ start() {
958
+ if (!this._protocol._server) {
959
+ if (this._protocol._debug) {
960
+ let type;
961
+ switch (this.type) {
962
+ case 'group':
963
+ type = 'KEXDH_INIT';
964
+ break;
965
+ default:
966
+ type = 'KEXECDH_INIT';
967
+ }
968
+ this._protocol._debug(`Outbound: Sending ${type}`);
969
+ }
970
+
971
+ const pubKey = this.getPublicKey();
972
+
973
+ let p = this._protocol._packetRW.write.allocStartKEX;
974
+ const packet = this._protocol._packetRW.write.alloc(
975
+ 1 + 4 + pubKey.length,
976
+ true
977
+ );
978
+ packet[p] = MESSAGE.KEXDH_INIT;
979
+ writeUInt32BE(packet, pubKey.length, ++p);
980
+ packet.set(pubKey, p += 4);
981
+ this._protocol._cipher.encrypt(
982
+ this._protocol._packetRW.write.finalize(packet, true)
983
+ );
984
+ }
985
+ }
986
+ getPublicKey() {
987
+ this.generateKeys();
988
+
989
+ const key = this._public;
990
+
991
+ if (key)
992
+ return this.convertPublicKey(key);
993
+ }
994
+ convertPublicKey(key) {
995
+ let newKey;
996
+ let idx = 0;
997
+ let len = key.length;
998
+ while (key[idx] === 0x00) {
999
+ ++idx;
1000
+ --len;
1001
+ }
1002
+
1003
+ if (key[idx] & 0x80) {
1004
+ newKey = Buffer.allocUnsafe(1 + len);
1005
+ newKey[0] = 0;
1006
+ key.copy(newKey, 1, idx);
1007
+ return newKey;
1008
+ }
1009
+
1010
+ if (len !== key.length) {
1011
+ newKey = Buffer.allocUnsafe(len);
1012
+ key.copy(newKey, 0, idx);
1013
+ key = newKey;
1014
+ }
1015
+ return key;
1016
+ }
1017
+ computeSecret(otherPublicKey) {
1018
+ this.generateKeys();
1019
+
1020
+ try {
1021
+ return convertToMpint(this._dh.computeSecret(otherPublicKey));
1022
+ } catch (ex) {
1023
+ return ex;
1024
+ }
1025
+ }
1026
+ parse(payload) {
1027
+ const type = payload[0];
1028
+ switch (this._step) {
1029
+ case 1:
1030
+ if (this._protocol._server) {
1031
+ // Server
1032
+ if (type !== MESSAGE.KEXDH_INIT) {
1033
+ return doFatalError(
1034
+ this._protocol,
1035
+ `Received packet ${type} instead of ${MESSAGE.KEXDH_INIT}`,
1036
+ 'handshake',
1037
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1038
+ );
1039
+ }
1040
+ this._protocol._debug && this._protocol._debug(
1041
+ 'Received DH Init'
1042
+ );
1043
+ /*
1044
+ byte SSH_MSG_KEXDH_INIT
1045
+ / SSH_MSG_KEX_ECDH_INIT
1046
+ string <method-specific data>
1047
+ */
1048
+ bufferParser.init(payload, 1);
1049
+ const dhData = bufferParser.readString();
1050
+ bufferParser.clear();
1051
+ if (dhData === undefined) {
1052
+ return doFatalError(
1053
+ this._protocol,
1054
+ 'Received malformed KEX*_INIT',
1055
+ 'handshake',
1056
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1057
+ );
1058
+ }
1059
+
1060
+ // Client public key
1061
+ this._dhData = dhData;
1062
+
1063
+ let hostKey =
1064
+ this._protocol._hostKeys[this.negotiated.serverHostKey];
1065
+ if (Array.isArray(hostKey))
1066
+ hostKey = hostKey[0];
1067
+ this._hostKey = hostKey;
1068
+
1069
+ this.finish();
1070
+ } else {
1071
+ // Client
1072
+ if (type !== MESSAGE.KEXDH_REPLY) {
1073
+ return doFatalError(
1074
+ this._protocol,
1075
+ `Received packet ${type} instead of ${MESSAGE.KEXDH_REPLY}`,
1076
+ 'handshake',
1077
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1078
+ );
1079
+ }
1080
+ this._protocol._debug && this._protocol._debug(
1081
+ 'Received DH Reply'
1082
+ );
1083
+ /*
1084
+ byte SSH_MSG_KEXDH_REPLY
1085
+ / SSH_MSG_KEX_DH_GEX_REPLY
1086
+ / SSH_MSG_KEX_ECDH_REPLY
1087
+ string server public host key and certificates (K_S)
1088
+ string <method-specific data>
1089
+ string signature of H
1090
+ */
1091
+ bufferParser.init(payload, 1);
1092
+ let hostPubKey;
1093
+ let dhData;
1094
+ let sig;
1095
+ if ((hostPubKey = bufferParser.readString()) === undefined
1096
+ || (dhData = bufferParser.readString()) === undefined
1097
+ || (sig = bufferParser.readString()) === undefined) {
1098
+ bufferParser.clear();
1099
+ return doFatalError(
1100
+ this._protocol,
1101
+ 'Received malformed KEX*_REPLY',
1102
+ 'handshake',
1103
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1104
+ );
1105
+ }
1106
+ bufferParser.clear();
1107
+
1108
+ // Check that the host public key type matches what was negotiated
1109
+ // during KEXINIT swap
1110
+ bufferParser.init(hostPubKey, 0);
1111
+ const hostPubKeyType = bufferParser.readString(true);
1112
+ bufferParser.clear();
1113
+ if (hostPubKeyType === undefined) {
1114
+ return doFatalError(
1115
+ this._protocol,
1116
+ 'Received malformed host public key',
1117
+ 'handshake',
1118
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1119
+ );
1120
+ }
1121
+ if (hostPubKeyType !== this.negotiated.serverHostKey) {
1122
+ // Check if we need to make an exception
1123
+ switch (this.negotiated.serverHostKey) {
1124
+ case 'rsa-sha2-256':
1125
+ case 'rsa-sha2-512':
1126
+ if (hostPubKeyType === 'ssh-rsa')
1127
+ break;
1128
+ // FALLTHROUGH
1129
+ default:
1130
+ return doFatalError(
1131
+ this._protocol,
1132
+ 'Host key does not match negotiated type',
1133
+ 'handshake',
1134
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1135
+ );
1136
+ }
1137
+ }
1138
+
1139
+ this._hostKey = hostPubKey;
1140
+ this._dhData = dhData;
1141
+ this._sig = sig;
1142
+
1143
+ let checked = false;
1144
+ let ret;
1145
+ if (this._protocol._hostVerifier === undefined) {
1146
+ ret = true;
1147
+ this._protocol._debug && this._protocol._debug(
1148
+ 'Host accepted by default (no verification)'
1149
+ );
1150
+ } else {
1151
+ ret = this._protocol._hostVerifier(hostPubKey, (permitted) => {
1152
+ if (checked)
1153
+ return;
1154
+ checked = true;
1155
+ if (permitted === false) {
1156
+ this._protocol._debug && this._protocol._debug(
1157
+ 'Host denied (verification failed)'
1158
+ );
1159
+ return doFatalError(
1160
+ this._protocol,
1161
+ 'Host denied (verification failed)',
1162
+ 'handshake',
1163
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1164
+ );
1165
+ }
1166
+ this._protocol._debug && this._protocol._debug(
1167
+ 'Host accepted (verified)'
1168
+ );
1169
+ this._hostVerified = true;
1170
+ if (this._receivedNEWKEYS)
1171
+ this.finish();
1172
+ else
1173
+ trySendNEWKEYS(this);
1174
+ });
1175
+ }
1176
+ if (ret === undefined) {
1177
+ // Async host verification
1178
+ ++this._step;
1179
+ return;
1180
+ }
1181
+ checked = true;
1182
+ if (ret === false) {
1183
+ this._protocol._debug && this._protocol._debug(
1184
+ 'Host denied (verification failed)'
1185
+ );
1186
+ return doFatalError(
1187
+ this._protocol,
1188
+ 'Host denied (verification failed)',
1189
+ 'handshake',
1190
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1191
+ );
1192
+ }
1193
+ this._protocol._debug && this._protocol._debug(
1194
+ 'Host accepted (verified)'
1195
+ );
1196
+ this._hostVerified = true;
1197
+ trySendNEWKEYS(this);
1198
+ }
1199
+ ++this._step;
1200
+ break;
1201
+ case 2:
1202
+ if (type !== MESSAGE.NEWKEYS) {
1203
+ return doFatalError(
1204
+ this._protocol,
1205
+ `Received packet ${type} instead of ${MESSAGE.NEWKEYS}`,
1206
+ 'handshake',
1207
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1208
+ );
1209
+ }
1210
+ this._protocol._debug && this._protocol._debug(
1211
+ 'Inbound: NEWKEYS'
1212
+ );
1213
+ this._receivedNEWKEYS = true;
1214
+ ++this._step;
1215
+ if (this._protocol._server || this._hostVerified)
1216
+ return this.finish();
1217
+
1218
+ // Signal to current decipher that we need to change to a new decipher
1219
+ // for the next packet
1220
+ return false;
1221
+ default:
1222
+ return doFatalError(
1223
+ this._protocol,
1224
+ `Received unexpected packet ${type} after NEWKEYS`,
1225
+ 'handshake',
1226
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1227
+ );
1228
+ }
1229
+ }
1230
+ }
1231
+
1232
+ class Curve25519Exchange extends KeyExchange {
1233
+ constructor(hashName, ...args) {
1234
+ super(...args);
1235
+
1236
+ this.type = '25519';
1237
+ this.hashName = hashName;
1238
+ this._keys = null;
1239
+ }
1240
+ generateKeys() {
1241
+ if (!this._keys)
1242
+ this._keys = generateKeyPairSync('x25519');
1243
+ }
1244
+ getPublicKey() {
1245
+ this.generateKeys();
1246
+
1247
+ const key = this._keys.publicKey.export({ type: 'spki', format: 'der' });
1248
+ return key.slice(-32); // HACK: avoids parsing DER/BER header
1249
+ }
1250
+ convertPublicKey(key) {
1251
+ let newKey;
1252
+ let idx = 0;
1253
+ let len = key.length;
1254
+ while (key[idx] === 0x00) {
1255
+ ++idx;
1256
+ --len;
1257
+ }
1258
+
1259
+ if (key.length === 32)
1260
+ return key;
1261
+
1262
+ if (len !== key.length) {
1263
+ newKey = Buffer.allocUnsafe(len);
1264
+ key.copy(newKey, 0, idx);
1265
+ key = newKey;
1266
+ }
1267
+ return key;
1268
+ }
1269
+ computeSecret(otherPublicKey) {
1270
+ this.generateKeys();
1271
+
1272
+ try {
1273
+ const asnWriter = new Ber.Writer();
1274
+ asnWriter.startSequence();
1275
+ // algorithm
1276
+ asnWriter.startSequence();
1277
+ asnWriter.writeOID('1.3.101.110'); // id-X25519
1278
+ asnWriter.endSequence();
1279
+
1280
+ // PublicKey
1281
+ asnWriter.startSequence(Ber.BitString);
1282
+ asnWriter.writeByte(0x00);
1283
+ // XXX: hack to write a raw buffer without a tag -- yuck
1284
+ asnWriter._ensure(otherPublicKey.length);
1285
+ otherPublicKey.copy(asnWriter._buf,
1286
+ asnWriter._offset,
1287
+ 0,
1288
+ otherPublicKey.length);
1289
+ asnWriter._offset += otherPublicKey.length;
1290
+ asnWriter.endSequence();
1291
+ asnWriter.endSequence();
1292
+
1293
+ return convertToMpint(diffieHellman({
1294
+ privateKey: this._keys.privateKey,
1295
+ publicKey: createPublicKey({
1296
+ key: asnWriter.buffer,
1297
+ type: 'spki',
1298
+ format: 'der',
1299
+ }),
1300
+ }));
1301
+ } catch (ex) {
1302
+ return ex;
1303
+ }
1304
+ }
1305
+ }
1306
+
1307
+ class ECDHExchange extends KeyExchange {
1308
+ constructor(curveName, hashName, ...args) {
1309
+ super(...args);
1310
+
1311
+ this.type = 'ecdh';
1312
+ this.curveName = curveName;
1313
+ this.hashName = hashName;
1314
+ }
1315
+ generateKeys() {
1316
+ if (!this._dh) {
1317
+ this._dh = createECDH(this.curveName);
1318
+ this._public = this._dh.generateKeys();
1319
+ }
1320
+ }
1321
+ }
1322
+
1323
+ class DHGroupExchange extends KeyExchange {
1324
+ constructor(hashName, ...args) {
1325
+ super(...args);
1326
+
1327
+ this.type = 'groupex';
1328
+ this.hashName = hashName;
1329
+ this._prime = null;
1330
+ this._generator = null;
1331
+ this._minBits = GEX_MIN_BITS;
1332
+ this._prefBits = dhEstimate(this.negotiated);
1333
+ if (this._protocol._compatFlags & COMPAT.BUG_DHGEX_LARGE)
1334
+ this._prefBits = Math.min(this._prefBits, 4096);
1335
+ this._maxBits = GEX_MAX_BITS;
1336
+ }
1337
+ start() {
1338
+ if (this._protocol._server)
1339
+ return;
1340
+ this._protocol._debug && this._protocol._debug(
1341
+ 'Outbound: Sending KEXDH_GEX_REQUEST'
1342
+ );
1343
+ let p = this._protocol._packetRW.write.allocStartKEX;
1344
+ const packet = this._protocol._packetRW.write.alloc(
1345
+ 1 + 4 + 4 + 4,
1346
+ true
1347
+ );
1348
+ packet[p] = MESSAGE.KEXDH_GEX_REQUEST;
1349
+ writeUInt32BE(packet, this._minBits, ++p);
1350
+ writeUInt32BE(packet, this._prefBits, p += 4);
1351
+ writeUInt32BE(packet, this._maxBits, p += 4);
1352
+ this._protocol._cipher.encrypt(
1353
+ this._protocol._packetRW.write.finalize(packet, true)
1354
+ );
1355
+ }
1356
+ generateKeys() {
1357
+ if (!this._dh && this._prime && this._generator) {
1358
+ this._dh = createDiffieHellman(this._prime, this._generator);
1359
+ this._public = this._dh.generateKeys();
1360
+ }
1361
+ }
1362
+ setDHParams(prime, generator) {
1363
+ if (!Buffer.isBuffer(prime))
1364
+ throw new Error('Invalid prime value');
1365
+ if (!Buffer.isBuffer(generator))
1366
+ throw new Error('Invalid generator value');
1367
+ this._prime = prime;
1368
+ this._generator = generator;
1369
+ }
1370
+ getDHParams() {
1371
+ if (this._dh) {
1372
+ return {
1373
+ prime: convertToMpint(this._dh.getPrime()),
1374
+ generator: convertToMpint(this._dh.getGenerator()),
1375
+ };
1376
+ }
1377
+ }
1378
+ parse(payload) {
1379
+ const type = payload[0];
1380
+ switch (this._step) {
1381
+ case 1:
1382
+ if (this._protocol._server) {
1383
+ if (type !== MESSAGE.KEXDH_GEX_REQUEST) {
1384
+ return doFatalError(
1385
+ this._protocol,
1386
+ `Received packet ${type} instead of `
1387
+ + MESSAGE.KEXDH_GEX_REQUEST,
1388
+ 'handshake',
1389
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1390
+ );
1391
+ }
1392
+ // TODO: allow user implementation to provide safe prime and
1393
+ // generator on demand to support group exchange on server side
1394
+ return doFatalError(
1395
+ this._protocol,
1396
+ 'Group exchange not implemented for server',
1397
+ 'handshake',
1398
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1399
+ );
1400
+ }
1401
+
1402
+ if (type !== MESSAGE.KEXDH_GEX_GROUP) {
1403
+ return doFatalError(
1404
+ this._protocol,
1405
+ `Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_GROUP}`,
1406
+ 'handshake',
1407
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1408
+ );
1409
+ }
1410
+
1411
+ this._protocol._debug && this._protocol._debug(
1412
+ 'Received DH GEX Group'
1413
+ );
1414
+
1415
+ /*
1416
+ byte SSH_MSG_KEX_DH_GEX_GROUP
1417
+ mpint p, safe prime
1418
+ mpint g, generator for subgroup in GF(p)
1419
+ */
1420
+ bufferParser.init(payload, 1);
1421
+ let prime;
1422
+ let gen;
1423
+ if ((prime = bufferParser.readString()) === undefined
1424
+ || (gen = bufferParser.readString()) === undefined) {
1425
+ bufferParser.clear();
1426
+ return doFatalError(
1427
+ this._protocol,
1428
+ 'Received malformed KEXDH_GEX_GROUP',
1429
+ 'handshake',
1430
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1431
+ );
1432
+ }
1433
+ bufferParser.clear();
1434
+
1435
+ // TODO: validate prime
1436
+ this.setDHParams(prime, gen);
1437
+ this.generateKeys();
1438
+ const pubkey = this.getPublicKey();
1439
+
1440
+ this._protocol._debug && this._protocol._debug(
1441
+ 'Outbound: Sending KEXDH_GEX_INIT'
1442
+ );
1443
+
1444
+ let p = this._protocol._packetRW.write.allocStartKEX;
1445
+ const packet =
1446
+ this._protocol._packetRW.write.alloc(1 + 4 + pubkey.length, true);
1447
+ packet[p] = MESSAGE.KEXDH_GEX_INIT;
1448
+ writeUInt32BE(packet, pubkey.length, ++p);
1449
+ packet.set(pubkey, p += 4);
1450
+ this._protocol._cipher.encrypt(
1451
+ this._protocol._packetRW.write.finalize(packet, true)
1452
+ );
1453
+
1454
+ ++this._step;
1455
+ break;
1456
+ case 2:
1457
+ if (this._protocol._server) {
1458
+ if (type !== MESSAGE.KEXDH_GEX_INIT) {
1459
+ return doFatalError(
1460
+ this._protocol,
1461
+ `Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_INIT}`,
1462
+ 'handshake',
1463
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1464
+ );
1465
+ }
1466
+ this._protocol._debug && this._protocol._debug(
1467
+ 'Received DH GEX Init'
1468
+ );
1469
+ return doFatalError(
1470
+ this._protocol,
1471
+ 'Group exchange not implemented for server',
1472
+ 'handshake',
1473
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1474
+ );
1475
+ } else if (type !== MESSAGE.KEXDH_GEX_REPLY) {
1476
+ return doFatalError(
1477
+ this._protocol,
1478
+ `Received packet ${type} instead of ${MESSAGE.KEXDH_GEX_REPLY}`,
1479
+ 'handshake',
1480
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1481
+ );
1482
+ }
1483
+ this._protocol._debug && this._protocol._debug(
1484
+ 'Received DH GEX Reply'
1485
+ );
1486
+ this._step = 1;
1487
+ payload[0] = MESSAGE.KEXDH_REPLY;
1488
+ this.parse = KeyExchange.prototype.parse;
1489
+ this.parse(payload);
1490
+ }
1491
+ }
1492
+ }
1493
+
1494
+ class DHExchange extends KeyExchange {
1495
+ constructor(groupName, hashName, ...args) {
1496
+ super(...args);
1497
+
1498
+ this.type = 'group';
1499
+ this.groupName = groupName;
1500
+ this.hashName = hashName;
1501
+ }
1502
+ start() {
1503
+ if (!this._protocol._server) {
1504
+ this._protocol._debug && this._protocol._debug(
1505
+ 'Outbound: Sending KEXDH_INIT'
1506
+ );
1507
+ const pubKey = this.getPublicKey();
1508
+ let p = this._protocol._packetRW.write.allocStartKEX;
1509
+ const packet =
1510
+ this._protocol._packetRW.write.alloc(1 + 4 + pubKey.length, true);
1511
+ packet[p] = MESSAGE.KEXDH_INIT;
1512
+ writeUInt32BE(packet, pubKey.length, ++p);
1513
+ packet.set(pubKey, p += 4);
1514
+ this._protocol._cipher.encrypt(
1515
+ this._protocol._packetRW.write.finalize(packet, true)
1516
+ );
1517
+ }
1518
+ }
1519
+ generateKeys() {
1520
+ if (!this._dh) {
1521
+ this._dh = createDiffieHellmanGroup(this.groupName);
1522
+ this._public = this._dh.generateKeys();
1523
+ }
1524
+ }
1525
+ getDHParams() {
1526
+ if (this._dh) {
1527
+ return {
1528
+ prime: convertToMpint(this._dh.getPrime()),
1529
+ generator: convertToMpint(this._dh.getGenerator()),
1530
+ };
1531
+ }
1532
+ }
1533
+ }
1534
+
1535
+ return (negotiated, ...args) => {
1536
+ if (typeof negotiated !== 'object' || negotiated === null)
1537
+ throw new Error('Invalid negotiated argument');
1538
+ const kexType = negotiated.kex;
1539
+ if (typeof kexType === 'string') {
1540
+ args = [negotiated, ...args];
1541
+ switch (kexType) {
1542
+ case 'curve25519-sha256':
1543
+ case 'curve25519-sha256@libssh.org':
1544
+ if (!curve25519Supported)
1545
+ break;
1546
+ return new Curve25519Exchange('sha256', ...args);
1547
+
1548
+ case 'ecdh-sha2-nistp256':
1549
+ return new ECDHExchange('prime256v1', 'sha256', ...args);
1550
+ case 'ecdh-sha2-nistp384':
1551
+ return new ECDHExchange('secp384r1', 'sha384', ...args);
1552
+ case 'ecdh-sha2-nistp521':
1553
+ return new ECDHExchange('secp521r1', 'sha512', ...args);
1554
+
1555
+ case 'diffie-hellman-group1-sha1':
1556
+ return new DHExchange('modp2', 'sha1', ...args);
1557
+ case 'diffie-hellman-group14-sha1':
1558
+ return new DHExchange('modp14', 'sha1', ...args);
1559
+ case 'diffie-hellman-group14-sha256':
1560
+ return new DHExchange('modp14', 'sha256', ...args);
1561
+ case 'diffie-hellman-group15-sha512':
1562
+ return new DHExchange('modp15', 'sha512', ...args);
1563
+ case 'diffie-hellman-group16-sha512':
1564
+ return new DHExchange('modp16', 'sha512', ...args);
1565
+ case 'diffie-hellman-group17-sha512':
1566
+ return new DHExchange('modp17', 'sha512', ...args);
1567
+ case 'diffie-hellman-group18-sha512':
1568
+ return new DHExchange('modp18', 'sha512', ...args);
1569
+
1570
+ case 'diffie-hellman-group-exchange-sha1':
1571
+ return new DHGroupExchange('sha1', ...args);
1572
+ case 'diffie-hellman-group-exchange-sha256':
1573
+ return new DHGroupExchange('sha256', ...args);
1574
+ }
1575
+ throw new Error(`Unsupported key exchange algorithm: ${kexType}`);
1576
+ }
1577
+ throw new Error(`Invalid key exchange type: ${kexType}`);
1578
+ };
1579
+ })();
1580
+
1581
+ const KexInit = (() => {
1582
+ const KEX_PROPERTY_NAMES = [
1583
+ 'kex',
1584
+ 'serverHostKey',
1585
+ ['cs', 'cipher' ],
1586
+ ['sc', 'cipher' ],
1587
+ ['cs', 'mac' ],
1588
+ ['sc', 'mac' ],
1589
+ ['cs', 'compress' ],
1590
+ ['sc', 'compress' ],
1591
+ ['cs', 'lang' ],
1592
+ ['sc', 'lang' ],
1593
+ ];
1594
+ return class KexInit {
1595
+ constructor(obj) {
1596
+ if (typeof obj !== 'object' || obj === null)
1597
+ throw new TypeError('Argument must be an object');
1598
+
1599
+ const lists = {
1600
+ kex: undefined,
1601
+ serverHostKey: undefined,
1602
+ cs: {
1603
+ cipher: undefined,
1604
+ mac: undefined,
1605
+ compress: undefined,
1606
+ lang: undefined,
1607
+ },
1608
+ sc: {
1609
+ cipher: undefined,
1610
+ mac: undefined,
1611
+ compress: undefined,
1612
+ lang: undefined,
1613
+ },
1614
+
1615
+ all: undefined,
1616
+ };
1617
+ let totalSize = 0;
1618
+ for (const prop of KEX_PROPERTY_NAMES) {
1619
+ let base;
1620
+ let val;
1621
+ let desc;
1622
+ let key;
1623
+ if (typeof prop === 'string') {
1624
+ base = lists;
1625
+ val = obj[prop];
1626
+ desc = key = prop;
1627
+ } else {
1628
+ const parent = prop[0];
1629
+ base = lists[parent];
1630
+ key = prop[1];
1631
+ val = obj[parent][key];
1632
+ desc = `${parent}.${key}`;
1633
+ }
1634
+ const entry = { array: undefined, buffer: undefined };
1635
+ if (Buffer.isBuffer(val)) {
1636
+ entry.array = ('' + val).split(',');
1637
+ entry.buffer = val;
1638
+ totalSize += 4 + val.length;
1639
+ } else {
1640
+ if (typeof val === 'string')
1641
+ val = val.split(',');
1642
+ if (Array.isArray(val)) {
1643
+ entry.array = val;
1644
+ entry.buffer = Buffer.from(val.join(','));
1645
+ } else {
1646
+ throw new TypeError(`Invalid \`${desc}\` type: ${typeof val}`);
1647
+ }
1648
+ totalSize += 4 + entry.buffer.length;
1649
+ }
1650
+ base[key] = entry;
1651
+ }
1652
+
1653
+ const all = Buffer.allocUnsafe(totalSize);
1654
+ lists.all = all;
1655
+
1656
+ let allPos = 0;
1657
+ for (const prop of KEX_PROPERTY_NAMES) {
1658
+ let data;
1659
+ if (typeof prop === 'string')
1660
+ data = lists[prop].buffer;
1661
+ else
1662
+ data = lists[prop[0]][prop[1]].buffer;
1663
+ allPos = writeUInt32BE(all, data.length, allPos);
1664
+ all.set(data, allPos);
1665
+ allPos += data.length;
1666
+ }
1667
+
1668
+ this.totalSize = totalSize;
1669
+ this.lists = lists;
1670
+ }
1671
+ copyAllTo(buf, offset) {
1672
+ const src = this.lists.all;
1673
+ if (typeof offset !== 'number')
1674
+ throw new TypeError(`Invalid offset value: ${typeof offset}`);
1675
+ if (buf.length - offset < src.length)
1676
+ throw new Error('Insufficient space to copy list');
1677
+ buf.set(src, offset);
1678
+ return src.length;
1679
+ }
1680
+ };
1681
+ })();
1682
+
1683
+ const hashString = (() => {
1684
+ const LEN = Buffer.allocUnsafe(4);
1685
+ return (hash, buf) => {
1686
+ writeUInt32BE(LEN, buf.length, 0);
1687
+ hash.update(LEN);
1688
+ hash.update(buf);
1689
+ };
1690
+ })();
1691
+
1692
+ function generateKEXVal(len, hashName, secret, exchangeHash, sessionID, char) {
1693
+ let ret;
1694
+ if (len) {
1695
+ let digest = createHash(hashName)
1696
+ .update(secret)
1697
+ .update(exchangeHash)
1698
+ .update(char)
1699
+ .update(sessionID)
1700
+ .digest();
1701
+ while (digest.length < len) {
1702
+ const chunk = createHash(hashName)
1703
+ .update(secret)
1704
+ .update(exchangeHash)
1705
+ .update(digest)
1706
+ .digest();
1707
+ const extended = Buffer.allocUnsafe(digest.length + chunk.length);
1708
+ extended.set(digest, 0);
1709
+ extended.set(chunk, digest.length);
1710
+ digest = extended;
1711
+ }
1712
+ if (digest.length === len)
1713
+ ret = digest;
1714
+ else
1715
+ ret = new FastBuffer(digest.buffer, digest.byteOffset, len);
1716
+ } else {
1717
+ ret = EMPTY_BUFFER;
1718
+ }
1719
+ return ret;
1720
+ }
1721
+
1722
+ function onKEXPayload(state, payload) {
1723
+ // XXX: move this to the Decipher implementations?
1724
+ if (payload.length === 0) {
1725
+ this._debug && this._debug('Inbound: Skipping empty packet payload');
1726
+ return;
1727
+ }
1728
+
1729
+ if (this._skipNextInboundPacket) {
1730
+ this._skipNextInboundPacket = false;
1731
+ return;
1732
+ }
1733
+
1734
+ payload = this._packetRW.read.read(payload);
1735
+
1736
+ const type = payload[0];
1737
+ switch (type) {
1738
+ case MESSAGE.DISCONNECT:
1739
+ case MESSAGE.IGNORE:
1740
+ case MESSAGE.UNIMPLEMENTED:
1741
+ case MESSAGE.DEBUG:
1742
+ if (!MESSAGE_HANDLERS)
1743
+ MESSAGE_HANDLERS = require('./handlers.js');
1744
+ return MESSAGE_HANDLERS[type](this, payload);
1745
+ case MESSAGE.KEXINIT:
1746
+ if (!state.firstPacket) {
1747
+ return doFatalError(
1748
+ this,
1749
+ 'Received extra KEXINIT during handshake',
1750
+ 'handshake',
1751
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1752
+ );
1753
+ }
1754
+ state.firstPacket = false;
1755
+ return handleKexInit(this, payload);
1756
+ default:
1757
+ if (type < 20 || type > 49) {
1758
+ return doFatalError(
1759
+ this,
1760
+ `Received unexpected packet type ${type}`,
1761
+ 'handshake',
1762
+ DISCONNECT_REASON.KEY_EXCHANGE_FAILED
1763
+ );
1764
+ }
1765
+ }
1766
+
1767
+ return this._kex.parse(payload);
1768
+ }
1769
+
1770
+ function dhEstimate(neg) {
1771
+ const csCipher = CIPHER_INFO[neg.cs.cipher];
1772
+ const scCipher = CIPHER_INFO[neg.sc.cipher];
1773
+ // XXX: if OpenSSH's `umac-*` MACs are ever supported, their key lengths will
1774
+ // also need to be considered when calculating `bits`
1775
+ const bits = Math.max(
1776
+ 0,
1777
+ (csCipher.sslName === 'des-ede3-cbc' ? 14 : csCipher.keyLen),
1778
+ csCipher.blockLen,
1779
+ csCipher.ivLen,
1780
+ (scCipher.sslName === 'des-ede3-cbc' ? 14 : scCipher.keyLen),
1781
+ scCipher.blockLen,
1782
+ scCipher.ivLen
1783
+ ) * 8;
1784
+ if (bits <= 112)
1785
+ return 2048;
1786
+ if (bits <= 128)
1787
+ return 3072;
1788
+ if (bits <= 192)
1789
+ return 7680;
1790
+ return 8192;
1791
+ }
1792
+
1793
+ function trySendNEWKEYS(kex) {
1794
+ if (!kex._sentNEWKEYS) {
1795
+ kex._protocol._debug && kex._protocol._debug(
1796
+ 'Outbound: Sending NEWKEYS'
1797
+ );
1798
+ const p = kex._protocol._packetRW.write.allocStartKEX;
1799
+ const packet = kex._protocol._packetRW.write.alloc(1, true);
1800
+ packet[p] = MESSAGE.NEWKEYS;
1801
+ kex._protocol._cipher.encrypt(
1802
+ kex._protocol._packetRW.write.finalize(packet, true)
1803
+ );
1804
+ kex._sentNEWKEYS = true;
1805
+ }
1806
+ }
1807
+
1808
+ module.exports = {
1809
+ KexInit,
1810
+ kexinit,
1811
+ onKEXPayload,
1812
+ DEFAULT_KEXINIT: new KexInit({
1813
+ kex: DEFAULT_KEX,
1814
+ serverHostKey: DEFAULT_SERVER_HOST_KEY,
1815
+ cs: {
1816
+ cipher: DEFAULT_CIPHER,
1817
+ mac: DEFAULT_MAC,
1818
+ compress: DEFAULT_COMPRESSION,
1819
+ lang: [],
1820
+ },
1821
+ sc: {
1822
+ cipher: DEFAULT_CIPHER,
1823
+ mac: DEFAULT_MAC,
1824
+ compress: DEFAULT_COMPRESSION,
1825
+ lang: [],
1826
+ },
1827
+ }),
1828
+ HANDLERS: {
1829
+ [MESSAGE.KEXINIT]: handleKexInit,
1830
+ },
1831
+ };