@electerm/ssh2 1.2.1 → 1.9.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/install.js CHANGED
@@ -2,19 +2,26 @@
2
2
 
3
3
  const { spawnSync } = require('child_process');
4
4
 
5
+ const forceFailOnNonZero = (process.env.CI_CHECK_FAIL === 'ssh2');
6
+
5
7
  // Attempt to build the bundled optional binding
6
- const result = spawnSync('node-gyp', [
8
+ const args = [
7
9
  `--target=${process.version}`,
8
- 'rebuild'
9
- ], {
10
+ `--real_openssl_major=${/^\d+/.exec(process.versions.openssl)[0]}`,
11
+ 'rebuild',
12
+ ];
13
+ const result = spawnSync('node-gyp', args, {
10
14
  cwd: 'lib/protocol/crypto',
11
15
  encoding: 'utf8',
12
16
  shell: true,
13
17
  stdio: 'inherit',
14
18
  windowsHide: true,
15
19
  });
16
- if (result.error || result.status !== 0)
20
+ if (result.error || result.status !== 0) {
17
21
  console.log('Failed to build optional crypto binding');
18
- else
22
+ if (forceFailOnNonZero)
23
+ process.exit(1);
24
+ } else {
19
25
  console.log('Succeeded in building optional crypto binding');
26
+ }
20
27
  process.exit(0);
package/lib/Channel.js CHANGED
@@ -214,6 +214,7 @@ class Channel extends DuplexStream {
214
214
  destroy() {
215
215
  this.end();
216
216
  this.close();
217
+ return this;
217
218
  }
218
219
 
219
220
  // Session type-specific methods =============================================
package/lib/client.js CHANGED
@@ -707,6 +707,7 @@ class Client extends EventEmitter {
707
707
  this.emit('connect');
708
708
 
709
709
  cryptoInit.then(() => {
710
+ proto.start();
710
711
  sock.on('data', (data) => {
711
712
  try {
712
713
  proto.parse(data, 0, data.length);
@@ -260,14 +260,15 @@ class Protocol {
260
260
  this._debug('Custom crypto binding not available');
261
261
  }
262
262
 
263
- process.nextTick(() => {
264
- this._debug && this._debug(
265
- `Local ident: ${inspect(this._identRaw.toString())}`
266
- );
263
+ this._debug && this._debug(
264
+ `Local ident: ${inspect(this._identRaw.toString())}`
265
+ );
266
+ this.start = () => {
267
+ this.start = undefined;
267
268
  if (greeting)
268
269
  this._onWrite(greeting);
269
270
  this._onWrite(sentIdent);
270
- });
271
+ };
271
272
  }
272
273
  _destruct(reason) {
273
274
  this._packetRW.read.cleanup();
@@ -1669,6 +1670,9 @@ class Protocol {
1669
1670
 
1670
1671
  const origSignal = name;
1671
1672
 
1673
+ if (typeof origSignal !== 'string' || !origSignal)
1674
+ throw new Error(`Invalid signal: ${origSignal}`);
1675
+
1672
1676
  let signal = name.toUpperCase();
1673
1677
  if (signal.slice(0, 3) === 'SIG')
1674
1678
  signal = signal.slice(3);
@@ -154,7 +154,14 @@ class SFTP extends EventEmitter {
154
154
  this._pktData = undefined;
155
155
  this._writeReqid = -1;
156
156
  this._requests = {};
157
- this._maxPktLen = (this._isOpenSSH ? OPENSSH_MAX_PKT_LEN : 34000);
157
+ this._maxInPktLen = OPENSSH_MAX_PKT_LEN;
158
+ this._maxOutPktLen = 34000;
159
+ this._maxReadLen =
160
+ (this._isOpenSSH ? OPENSSH_MAX_PKT_LEN : 34000) - PKT_RW_OVERHEAD;
161
+ this._maxWriteLen =
162
+ (this._isOpenSSH ? OPENSSH_MAX_PKT_LEN : 34000) - PKT_RW_OVERHEAD;
163
+
164
+ this.maxOpenHandles = undefined;
158
165
 
159
166
  // Channel compatibility
160
167
  this._client = client;
@@ -208,8 +215,8 @@ class SFTP extends EventEmitter {
208
215
  return;
209
216
  if (this._pktLen === 0)
210
217
  return doFatalSFTPError(this, 'Invalid packet length');
211
- if (this._pktLen > this._maxPktLen) {
212
- const max = this._maxPktLen;
218
+ if (this._pktLen > this._maxInPktLen) {
219
+ const max = this._maxInPktLen;
213
220
  return doFatalSFTPError(
214
221
  this,
215
222
  `Packet length ${this._pktLen} exceeds max length of ${max}`
@@ -432,7 +439,7 @@ class SFTP extends EventEmitter {
432
439
  return;
433
440
  }
434
441
 
435
- const maxDataLen = this._maxPktLen - PKT_RW_OVERHEAD;
442
+ const maxDataLen = this._maxWriteLen;
436
443
  const overflow = Math.max(len - maxDataLen, 0);
437
444
  const origPosition = position;
438
445
 
@@ -1421,7 +1428,7 @@ class SFTP extends EventEmitter {
1421
1428
  throw new Error('Client-only method called in server mode');
1422
1429
 
1423
1430
  const ext = this._extensions['hardlink@openssh.com'];
1424
- if (!ext || ext.indexOf('1') === -1)
1431
+ if (ext !== '1')
1425
1432
  throw new Error('Server does not support this extended request');
1426
1433
 
1427
1434
  /*
@@ -1461,7 +1468,7 @@ class SFTP extends EventEmitter {
1461
1468
  throw new Error('Client-only method called in server mode');
1462
1469
 
1463
1470
  const ext = this._extensions['fsync@openssh.com'];
1464
- if (!ext || ext.indexOf('1') === -1)
1471
+ if (ext !== '1')
1465
1472
  throw new Error('Server does not support this extended request');
1466
1473
  if (!Buffer.isBuffer(handle))
1467
1474
  throw new Error('handle is not a Buffer');
@@ -1492,6 +1499,103 @@ class SFTP extends EventEmitter {
1492
1499
  `SFTP: Outbound: ${isBuffered ? 'Buffered' : 'Sending'} fsync@openssh.com`
1493
1500
  );
1494
1501
  }
1502
+ ext_openssh_lsetstat(path, attrs, cb) {
1503
+ if (this.server)
1504
+ throw new Error('Client-only method called in server mode');
1505
+
1506
+ const ext = this._extensions['lsetstat@openssh.com'];
1507
+ if (ext !== '1')
1508
+ throw new Error('Server does not support this extended request');
1509
+
1510
+ let flags = 0;
1511
+ let attrsLen = 0;
1512
+
1513
+ if (typeof attrs === 'object' && attrs !== null) {
1514
+ attrs = attrsToBytes(attrs);
1515
+ flags = attrs.flags;
1516
+ attrsLen = attrs.nb;
1517
+ } else if (typeof attrs === 'function') {
1518
+ cb = attrs;
1519
+ }
1520
+
1521
+ /*
1522
+ uint32 id
1523
+ string "lsetstat@openssh.com"
1524
+ string path
1525
+ ATTRS attrs
1526
+ */
1527
+ const pathLen = Buffer.byteLength(path);
1528
+ let p = 9;
1529
+ const buf =
1530
+ Buffer.allocUnsafe(4 + 1 + 4 + 4 + 20 + 4 + pathLen + 4 + attrsLen);
1531
+
1532
+ writeUInt32BE(buf, buf.length - 4, 0);
1533
+ buf[4] = REQUEST.EXTENDED;
1534
+ const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
1535
+ writeUInt32BE(buf, reqid, 5);
1536
+
1537
+ writeUInt32BE(buf, 20, p);
1538
+ buf.utf8Write('lsetstat@openssh.com', p += 4, 20);
1539
+
1540
+ writeUInt32BE(buf, pathLen, p += 20);
1541
+ buf.utf8Write(path, p += 4, pathLen);
1542
+
1543
+ writeUInt32BE(buf, flags, p += pathLen);
1544
+ if (attrsLen) {
1545
+ p += 4;
1546
+
1547
+ if (attrsLen === ATTRS_BUF.length)
1548
+ buf.set(ATTRS_BUF, p);
1549
+ else
1550
+ bufferCopy(ATTRS_BUF, buf, 0, attrsLen, p);
1551
+
1552
+ p += attrsLen;
1553
+ }
1554
+
1555
+ this._requests[reqid] = { cb };
1556
+
1557
+ const isBuffered = sendOrBuffer(this, buf);
1558
+ if (this._debug) {
1559
+ const status = (isBuffered ? 'Buffered' : 'Sending');
1560
+ this._debug(`SFTP: Outbound: ${status} lsetstat@openssh.com`);
1561
+ }
1562
+ }
1563
+ ext_openssh_expandPath(path, cb) {
1564
+ if (this.server)
1565
+ throw new Error('Client-only method called in server mode');
1566
+
1567
+ const ext = this._extensions['expand-path@openssh.com'];
1568
+ if (ext !== '1')
1569
+ throw new Error('Server does not support this extended request');
1570
+
1571
+ /*
1572
+ uint32 id
1573
+ string "expand-path@openssh.com"
1574
+ string path
1575
+ */
1576
+ const pathLen = Buffer.byteLength(path);
1577
+ let p = 9;
1578
+ const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 23 + 4 + pathLen);
1579
+
1580
+ writeUInt32BE(buf, buf.length - 4, 0);
1581
+ buf[4] = REQUEST.EXTENDED;
1582
+ const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
1583
+ writeUInt32BE(buf, reqid, 5);
1584
+
1585
+ writeUInt32BE(buf, 23, p);
1586
+ buf.utf8Write('expand-path@openssh.com', p += 4, 23);
1587
+
1588
+ writeUInt32BE(buf, pathLen, p += 20);
1589
+ buf.utf8Write(path, p += 4, pathLen);
1590
+
1591
+ this._requests[reqid] = { cb };
1592
+
1593
+ const isBuffered = sendOrBuffer(this, buf);
1594
+ if (this._debug) {
1595
+ const status = (isBuffered ? 'Buffered' : 'Sending');
1596
+ this._debug(`SFTP: Outbound: ${status} expand-path@openssh.com`);
1597
+ }
1598
+ }
1495
1599
  // ===========================================================================
1496
1600
  // Server-specific ===========================================================
1497
1601
  // ===========================================================================
@@ -1760,7 +1864,7 @@ function tryCreateBuffer(size) {
1760
1864
  }
1761
1865
 
1762
1866
  function read_(self, handle, buf, off, len, position, cb, req_) {
1763
- const maxDataLen = self._maxPktLen - PKT_RW_OVERHEAD;
1867
+ const maxDataLen = self._maxReadLen;
1764
1868
  const overflow = Math.max(len - maxDataLen, 0);
1765
1869
 
1766
1870
  if (overflow)
@@ -2394,6 +2498,31 @@ function cleanupRequests(sftp) {
2394
2498
  }
2395
2499
  }
2396
2500
 
2501
+ function requestLimits(sftp, cb) {
2502
+ /*
2503
+ uint32 id
2504
+ string "limits@openssh.com"
2505
+ */
2506
+ let p = 9;
2507
+ const buf = Buffer.allocUnsafe(4 + 1 + 4 + 4 + 18);
2508
+
2509
+ writeUInt32BE(buf, buf.length - 4, 0);
2510
+ buf[4] = REQUEST.EXTENDED;
2511
+ const reqid = sftp._writeReqid = (sftp._writeReqid + 1) & MAX_REQID;
2512
+ writeUInt32BE(buf, reqid, 5);
2513
+
2514
+ writeUInt32BE(buf, 18, p);
2515
+ buf.utf8Write('limits@openssh.com', p += 4, 18);
2516
+
2517
+ sftp._requests[reqid] = { extended: 'limits@openssh.com', cb };
2518
+
2519
+ const isBuffered = sendOrBuffer(sftp, buf);
2520
+ if (sftp._debug) {
2521
+ const which = (isBuffered ? 'Buffered' : 'Sending');
2522
+ sftp._debug(`SFTP: Outbound: ${which} limits@openssh.com`);
2523
+ }
2524
+ }
2525
+
2397
2526
  const CLIENT_HANDLERS = {
2398
2527
  [RESPONSE.VERSION]: (sftp, payload) => {
2399
2528
  if (sftp._version !== -1)
@@ -2434,6 +2563,24 @@ const CLIENT_HANDLERS = {
2434
2563
 
2435
2564
  sftp._version = version;
2436
2565
  sftp._extensions = extensions;
2566
+
2567
+ if (extensions['limits@openssh.com'] === '1') {
2568
+ return requestLimits(sftp, (err, limits) => {
2569
+ if (!err) {
2570
+ if (limits.maxPktLen > 0)
2571
+ sftp._maxOutPktLen = limits.maxPktLen;
2572
+ if (limits.maxReadLen > 0)
2573
+ sftp._maxReadLen = limits.maxReadLen;
2574
+ if (limits.maxWriteLen > 0)
2575
+ sftp._maxWriteLen = limits.maxWriteLen;
2576
+ sftp.maxOpenHandles = (
2577
+ limits.maxOpenHandles > 0 ? limits.maxOpenHandles : Infinity
2578
+ );
2579
+ }
2580
+ sftp.emit('ready');
2581
+ });
2582
+ }
2583
+
2437
2584
  sftp.emit('ready');
2438
2585
  },
2439
2586
  [RESPONSE.STATUS]: (sftp, payload) => {
@@ -2446,14 +2593,13 @@ const CLIENT_HANDLERS = {
2446
2593
  */
2447
2594
  const errorCode = bufferParser.readUInt32BE();
2448
2595
  const errorMsg = bufferParser.readString(true);
2449
- const lang = bufferParser.skipString();
2450
2596
  bufferParser.clear();
2451
2597
 
2452
- if (lang === undefined) {
2453
- if (reqID !== undefined)
2454
- delete sftp._requests[reqID];
2455
- return doFatalSFTPError(sftp, 'Malformed STATUS packet');
2456
- }
2598
+ // Note: we avoid checking that the error message and language tag are in
2599
+ // the packet because there are some broken implementations that incorrectly
2600
+ // omit them. The language tag in general was never really used amongst ssh
2601
+ // implementations, so in the case of a missing error message we just
2602
+ // default to something sensible.
2457
2603
 
2458
2604
  if (sftp._debug) {
2459
2605
  const jsonMsg = JSON.stringify(errorMsg);
@@ -2669,6 +2815,32 @@ const CLIENT_HANDLERS = {
2669
2815
  req.cb(undefined, stats);
2670
2816
  return;
2671
2817
  }
2818
+ case 'limits@openssh.com': {
2819
+ /*
2820
+ uint64 max-packet-length
2821
+ uint64 max-read-length
2822
+ uint64 max-write-length
2823
+ uint64 max-open-handles
2824
+ */
2825
+ const limits = {
2826
+ maxPktLen: bufferParser.readUInt64BE(),
2827
+ maxReadLen: bufferParser.readUInt64BE(),
2828
+ maxWriteLen: bufferParser.readUInt64BE(),
2829
+ maxOpenHandles: bufferParser.readUInt64BE(),
2830
+ };
2831
+ if (limits.maxOpenHandles === undefined)
2832
+ break;
2833
+ if (sftp._debug) {
2834
+ sftp._debug(
2835
+ 'SFTP: Inbound: Received EXTENDED_REPLY '
2836
+ + `(id:${reqID}, ${req.extended})`
2837
+ );
2838
+ }
2839
+ bufferParser.clear();
2840
+ if (typeof req.cb === 'function')
2841
+ req.cb(undefined, limits);
2842
+ return;
2843
+ }
2672
2844
  default:
2673
2845
  // Unknown extended request
2674
2846
  sftp._debug && sftp._debug(
@@ -7,7 +7,7 @@ try {
7
7
  cpuInfo = require('cpu-features')();
8
8
  } catch {}
9
9
 
10
- const { bindingAvailable } = require('./crypto.js');
10
+ const { bindingAvailable, CIPHER_INFO, MAC_INFO } = require('./crypto.js');
11
11
 
12
12
  const eddsaSupported = (() => {
13
13
  if (typeof crypto.sign === 'function'
@@ -76,11 +76,13 @@ const SUPPORTED_SERVER_HOST_KEY = DEFAULT_SERVER_HOST_KEY.concat([
76
76
  ]);
77
77
 
78
78
 
79
- const DEFAULT_CIPHER = [
79
+ const canUseCipher = (() => {
80
+ const ciphers = crypto.getCiphers();
81
+ return (name) => ciphers.includes(CIPHER_INFO[name].sslName);
82
+ })();
83
+ let DEFAULT_CIPHER = [
80
84
  // http://tools.ietf.org/html/rfc5647
81
- 'aes128-gcm',
82
85
  'aes128-gcm@openssh.com',
83
- 'aes256-gcm',
84
86
  'aes256-gcm@openssh.com',
85
87
 
86
88
  // http://tools.ietf.org/html/rfc4344#section-4
@@ -101,12 +103,15 @@ if (cpuInfo && cpuInfo.flags && !cpuInfo.flags.aes) {
101
103
  } else {
102
104
  DEFAULT_CIPHER.push('chacha20-poly1305@openssh.com');
103
105
  }
106
+ DEFAULT_CIPHER = DEFAULT_CIPHER.filter(canUseCipher);
104
107
  const SUPPORTED_CIPHER = DEFAULT_CIPHER.concat([
105
108
  'aes256-cbc',
106
109
  'aes192-cbc',
107
110
  'aes128-cbc',
108
111
  'blowfish-cbc',
109
112
  '3des-cbc',
113
+ 'aes128-gcm',
114
+ 'aes256-gcm',
110
115
 
111
116
  // http://tools.ietf.org/html/rfc4345#section-4:
112
117
  'arcfour256',
@@ -114,9 +119,13 @@ const SUPPORTED_CIPHER = DEFAULT_CIPHER.concat([
114
119
 
115
120
  'cast128-cbc',
116
121
  'arcfour',
117
- ]);
122
+ ].filter(canUseCipher));
118
123
 
119
124
 
125
+ const canUseMAC = (() => {
126
+ const hashes = crypto.getHashes();
127
+ return (name) => hashes.includes(MAC_INFO[name].sslName);
128
+ })();
120
129
  const DEFAULT_MAC = [
121
130
  'hmac-sha2-256-etm@openssh.com',
122
131
  'hmac-sha2-512-etm@openssh.com',
@@ -124,7 +133,7 @@ const DEFAULT_MAC = [
124
133
  'hmac-sha2-256',
125
134
  'hmac-sha2-512',
126
135
  'hmac-sha1',
127
- ];
136
+ ].filter(canUseMAC);
128
137
  const SUPPORTED_MAC = DEFAULT_MAC.concat([
129
138
  'hmac-md5',
130
139
  'hmac-sha2-256-96', // first 96 bits of HMAC-SHA256
@@ -132,7 +141,7 @@ const SUPPORTED_MAC = DEFAULT_MAC.concat([
132
141
  'hmac-ripemd160',
133
142
  'hmac-sha1-96', // first 96 bits of HMAC-SHA1
134
143
  'hmac-md5-96', // first 96 bits of HMAC-MD5
135
- ]);
144
+ ].filter(canUseMAC));
136
145
 
137
146
  const DEFAULT_COMPRESSION = [
138
147
  'none',
@@ -1,4 +1,7 @@
1
1
  {
2
+ 'variables': {
3
+ 'real_openssl_major%': '0',
4
+ },
2
5
  'targets': [
3
6
  {
4
7
  'target_name': 'sshcrypto',
@@ -9,6 +12,12 @@
9
12
  'src/binding.cc'
10
13
  ],
11
14
  'cflags': [ '-O3' ],
15
+
16
+ # Needed for OpenSSL 3.x/node.js v17.x+
17
+ 'defines': [
18
+ 'OPENSSL_API_COMPAT=0x10100000L',
19
+ 'REAL_OPENSSL_MAJOR=<(real_openssl_major)',
20
+ ],
12
21
  },
13
22
  ],
14
23
  }
@@ -1,4 +1,3 @@
1
- // TODO: switch from obsolete EVP_* APIs in CCP
2
1
  #include <stdio.h>
3
2
  #include <string.h>
4
3
  #include <assert.h>
@@ -7,10 +6,34 @@
7
6
  #include <node_buffer.h>
8
7
  #include <nan.h>
9
8
 
9
+ #if NODE_MAJOR_VERSION >= 17
10
+ # include <openssl/configuration.h>
11
+ #endif
12
+
10
13
  #include <openssl/err.h>
11
14
  #include <openssl/evp.h>
12
15
  #include <openssl/hmac.h>
13
16
 
17
+ #ifndef _WIN32
18
+ # include <dlfcn.h>
19
+ #endif
20
+
21
+ typedef int (*ctx_iv_len_func)(const EVP_CIPHER_CTX*);
22
+ typedef int (*ctx_key_len_func)(const EVP_CIPHER_CTX*);
23
+ typedef int (*ctx_get_block_size_func)(const EVP_CIPHER_CTX*);
24
+ typedef int (*cipher_flags_func)(const EVP_CIPHER*);
25
+ ctx_iv_len_func ctx_iv_len = nullptr;
26
+ ctx_key_len_func ctx_key_len = nullptr;
27
+ ctx_get_block_size_func ctx_get_block_size = nullptr;
28
+ cipher_flags_func cipher_flags = nullptr;
29
+
30
+ #if REAL_OPENSSL_MAJOR < 3
31
+ # undef EVP_DigestSignUpdate
32
+ # define EVP_DigestSignUpdate EVP_DigestUpdate
33
+ # undef EVP_PKEY_OP_SIGNCTX
34
+ # define EVP_PKEY_OP_SIGNCTX (1 << 6)
35
+ #endif
36
+
14
37
  using namespace node;
15
38
  using namespace v8;
16
39
  using namespace std;
@@ -62,8 +85,14 @@ class ChaChaPolyCipher : public ObjectWrap {
62
85
  explicit ChaChaPolyCipher()
63
86
  : ctx_main_(nullptr),
64
87
  ctx_pktlen_(nullptr),
88
+ #if REAL_OPENSSL_MAJOR >= 3
89
+ mac_(nullptr),
90
+ mac_ctx_(nullptr) {}
91
+ #else
65
92
  md_ctx_(nullptr),
66
- polykey_(nullptr) {}
93
+ polykey_(nullptr),
94
+ polykey_ctx_(nullptr) {}
95
+ #endif
67
96
 
68
97
  ~ChaChaPolyCipher() {
69
98
  clear();
@@ -71,15 +100,23 @@ class ChaChaPolyCipher : public ObjectWrap {
71
100
 
72
101
  void clear() {
73
102
  if (ctx_pktlen_) {
74
- EVP_CIPHER_CTX_cleanup(ctx_pktlen_);
75
103
  EVP_CIPHER_CTX_free(ctx_pktlen_);
76
104
  ctx_pktlen_ = nullptr;
77
105
  }
78
106
  if (ctx_main_) {
79
- EVP_CIPHER_CTX_cleanup(ctx_main_);
80
107
  EVP_CIPHER_CTX_free(ctx_main_);
81
108
  ctx_main_ = nullptr;
82
109
  }
110
+ #if REAL_OPENSSL_MAJOR >= 3
111
+ if (mac_ctx_) {
112
+ EVP_MAC_CTX_free(mac_ctx_);
113
+ mac_ctx_ = nullptr;
114
+ }
115
+ if (mac_) {
116
+ EVP_MAC_free(mac_);
117
+ mac_ = nullptr;
118
+ }
119
+ #else
83
120
  if (polykey_) {
84
121
  EVP_PKEY_free(polykey_);
85
122
  polykey_ = nullptr;
@@ -90,6 +127,7 @@ class ChaChaPolyCipher : public ObjectWrap {
90
127
  }
91
128
  // `polykey_ctx_` is not explicitly freed as it is freed implicitly when
92
129
  // `md_ctx_` is freed
130
+ #endif
93
131
  }
94
132
 
95
133
  ErrorType init(unsigned char* keys, size_t keys_len) {
@@ -108,7 +146,14 @@ class ChaChaPolyCipher : public ObjectWrap {
108
146
 
109
147
  if ((ctx_pktlen_ = EVP_CIPHER_CTX_new()) == nullptr
110
148
  || (ctx_main_ = EVP_CIPHER_CTX_new()) == nullptr
149
+ #if REAL_OPENSSL_MAJOR >= 3
150
+ || (mac_ = EVP_MAC_fetch(nullptr,
151
+ "POLY1305",
152
+ "provider=default")) == nullptr
153
+ || (mac_ctx_ = EVP_MAC_CTX_new(mac_)) == nullptr
154
+ #else
111
155
  || (md_ctx_ = EVP_MD_CTX_new()) == nullptr
156
+ #endif
112
157
  || EVP_EncryptInit_ex(ctx_pktlen_,
113
158
  cipher,
114
159
  nullptr,
@@ -122,7 +167,7 @@ class ChaChaPolyCipher : public ObjectWrap {
122
167
  r = kErrOpenSSL;
123
168
  goto out;
124
169
  }
125
- if (EVP_CIPHER_CTX_iv_length(ctx_pktlen_) != 16) {
170
+ if (ctx_iv_len(ctx_pktlen_) != 16) {
126
171
  r = kErrBadIVLen;
127
172
  goto out;
128
173
  }
@@ -206,6 +251,14 @@ out:
206
251
  }
207
252
 
208
253
  // Poly1305 over ciphertext
254
+ #if REAL_OPENSSL_MAJOR >= 3
255
+ if (EVP_MAC_init(mac_ctx_, polykey, sizeof(polykey), nullptr) != 1
256
+ || EVP_MAC_update(mac_ctx_, packet, data_len) != 1
257
+ || EVP_MAC_final(mac_ctx_, packet + data_len, &sig_len, sig_len) != 1) {
258
+ r = kErrOpenSSL;
259
+ goto out;
260
+ }
261
+ #else
209
262
  if (polykey_) {
210
263
  if (EVP_PKEY_CTX_ctrl(polykey_ctx_,
211
264
  -1,
@@ -245,9 +298,10 @@ out:
245
298
  r = kErrOpenSSL;
246
299
  goto out;
247
300
  }
301
+ #endif
248
302
 
249
- out:
250
- return r;
303
+ out:
304
+ return r;
251
305
  }
252
306
 
253
307
  static NAN_METHOD(New) {
@@ -328,9 +382,14 @@ out:
328
382
 
329
383
  EVP_CIPHER_CTX* ctx_main_;
330
384
  EVP_CIPHER_CTX* ctx_pktlen_;
385
+ #if REAL_OPENSSL_MAJOR >= 3
386
+ EVP_MAC* mac_;
387
+ EVP_MAC_CTX* mac_ctx_;
388
+ #else
331
389
  EVP_MD_CTX* md_ctx_;
332
390
  EVP_PKEY* polykey_;
333
391
  EVP_PKEY_CTX* polykey_ctx_;
392
+ #endif
334
393
  };
335
394
 
336
395
  class AESGCMCipher : public ObjectWrap {
@@ -359,7 +418,6 @@ class AESGCMCipher : public ObjectWrap {
359
418
 
360
419
  void clear() {
361
420
  if (ctx_) {
362
- EVP_CIPHER_CTX_cleanup(ctx_);
363
421
  EVP_CIPHER_CTX_free(ctx_);
364
422
  ctx_ = nullptr;
365
423
  }
@@ -394,12 +452,12 @@ class AESGCMCipher : public ObjectWrap {
394
452
  goto out;
395
453
  }
396
454
 
397
- //~ if (iv_len != static_cast<size_t>(EVP_CIPHER_CTX_iv_length(ctx_))) {
455
+ //~ if (iv_len != static_cast<size_t>(ctx_iv_len(ctx_))) {
398
456
  //~ r = kErrBadIVLen;
399
457
  //~ goto out;
400
458
  //~ }
401
459
 
402
- if (key_len != static_cast<size_t>(EVP_CIPHER_CTX_key_length(ctx_))) {
460
+ if (key_len != static_cast<size_t>(ctx_key_len(ctx_))) {
403
461
  if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) {
404
462
  r = kErrBadKeyLen;
405
463
  goto out;
@@ -606,7 +664,6 @@ class GenericCipher : public ObjectWrap {
606
664
 
607
665
  void clear() {
608
666
  if (ctx_) {
609
- EVP_CIPHER_CTX_cleanup(ctx_);
610
667
  EVP_CIPHER_CTX_free(ctx_);
611
668
  ctx_ = nullptr;
612
669
  }
@@ -640,12 +697,12 @@ class GenericCipher : public ObjectWrap {
640
697
  goto out;
641
698
  }
642
699
 
643
- if (iv_len != static_cast<size_t>(EVP_CIPHER_CTX_iv_length(ctx_))) {
700
+ if (iv_len != static_cast<size_t>(ctx_iv_len(ctx_))) {
644
701
  r = kErrBadIVLen;
645
702
  goto out;
646
703
  }
647
704
 
648
- if (key_len != static_cast<size_t>(EVP_CIPHER_CTX_key_length(ctx_))) {
705
+ if (key_len != static_cast<size_t>(ctx_key_len(ctx_))) {
649
706
  if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) {
650
707
  r = kErrBadKeyLen;
651
708
  goto out;
@@ -930,8 +987,14 @@ class ChaChaPolyDecipher : public ObjectWrap {
930
987
  explicit ChaChaPolyDecipher()
931
988
  : ctx_main_(nullptr),
932
989
  ctx_pktlen_(nullptr),
990
+ #if REAL_OPENSSL_MAJOR >= 3
991
+ mac_(nullptr),
992
+ mac_ctx_(nullptr) {}
993
+ #else
933
994
  md_ctx_(nullptr),
934
- polykey_(nullptr) {}
995
+ polykey_(nullptr),
996
+ polykey_ctx_(nullptr) {}
997
+ #endif
935
998
 
936
999
  ~ChaChaPolyDecipher() {
937
1000
  clear();
@@ -939,15 +1002,23 @@ class ChaChaPolyDecipher : public ObjectWrap {
939
1002
 
940
1003
  void clear() {
941
1004
  if (ctx_pktlen_) {
942
- EVP_CIPHER_CTX_cleanup(ctx_pktlen_);
943
1005
  EVP_CIPHER_CTX_free(ctx_pktlen_);
944
1006
  ctx_pktlen_ = nullptr;
945
1007
  }
946
1008
  if (ctx_main_) {
947
- EVP_CIPHER_CTX_cleanup(ctx_main_);
948
1009
  EVP_CIPHER_CTX_free(ctx_main_);
949
1010
  ctx_main_ = nullptr;
950
1011
  }
1012
+ #if REAL_OPENSSL_MAJOR >= 3
1013
+ if (mac_ctx_) {
1014
+ EVP_MAC_CTX_free(mac_ctx_);
1015
+ mac_ctx_ = nullptr;
1016
+ }
1017
+ if (mac_) {
1018
+ EVP_MAC_free(mac_);
1019
+ mac_ = nullptr;
1020
+ }
1021
+ #else
951
1022
  if (polykey_) {
952
1023
  EVP_PKEY_free(polykey_);
953
1024
  polykey_ = nullptr;
@@ -958,6 +1029,7 @@ class ChaChaPolyDecipher : public ObjectWrap {
958
1029
  }
959
1030
  // `polykey_ctx_` is not explicitly freed as it is freed implicitly when
960
1031
  // `md_ctx_` is freed
1032
+ #endif
961
1033
  }
962
1034
 
963
1035
  ErrorType init(unsigned char* keys, size_t keys_len) {
@@ -976,7 +1048,14 @@ class ChaChaPolyDecipher : public ObjectWrap {
976
1048
 
977
1049
  if ((ctx_pktlen_ = EVP_CIPHER_CTX_new()) == nullptr
978
1050
  || (ctx_main_ = EVP_CIPHER_CTX_new()) == nullptr
1051
+ #if REAL_OPENSSL_MAJOR >= 3
1052
+ || (mac_ = EVP_MAC_fetch(nullptr,
1053
+ "POLY1305",
1054
+ "provider=default")) == nullptr
1055
+ || (mac_ctx_ = EVP_MAC_CTX_new(mac_)) == nullptr
1056
+ #else
979
1057
  || (md_ctx_ = EVP_MD_CTX_new()) == nullptr
1058
+ #endif
980
1059
  || EVP_DecryptInit_ex(ctx_pktlen_,
981
1060
  cipher,
982
1061
  nullptr,
@@ -990,7 +1069,7 @@ class ChaChaPolyDecipher : public ObjectWrap {
990
1069
  r = kErrOpenSSL;
991
1070
  goto out;
992
1071
  }
993
- if (EVP_CIPHER_CTX_iv_length(ctx_pktlen_) != 16) {
1072
+ if (ctx_iv_len(ctx_pktlen_) != 16) {
994
1073
  r = kErrBadIVLen;
995
1074
  goto out;
996
1075
  }
@@ -1083,6 +1162,15 @@ out:
1083
1162
  }
1084
1163
 
1085
1164
  // Poly1305 over ciphertext
1165
+ #if REAL_OPENSSL_MAJOR >= 3
1166
+ if (EVP_MAC_init(mac_ctx_, polykey, sizeof(polykey), nullptr) != 1
1167
+ || EVP_MAC_update(mac_ctx_, length_bytes, sizeof(length_bytes)) != 1
1168
+ || EVP_MAC_update(mac_ctx_, packet, packet_len) != 1
1169
+ || EVP_MAC_final(mac_ctx_, calc_mac, &sig_len, sig_len) != 1) {
1170
+ r = kErrOpenSSL;
1171
+ goto out;
1172
+ }
1173
+ #else
1086
1174
  if (polykey_) {
1087
1175
  if (EVP_PKEY_CTX_ctrl(polykey_ctx_,
1088
1176
  -1,
@@ -1128,6 +1216,7 @@ out:
1128
1216
  r = kErrOpenSSL;
1129
1217
  goto out;
1130
1218
  }
1219
+ #endif
1131
1220
 
1132
1221
  // Compare MACs
1133
1222
  if (CRYPTO_memcmp(mac, calc_mac, sizeof(calc_mac))) {
@@ -1289,9 +1378,14 @@ out:
1289
1378
  unsigned char length_bytes[4];
1290
1379
  EVP_CIPHER_CTX* ctx_main_;
1291
1380
  EVP_CIPHER_CTX* ctx_pktlen_;
1381
+ #if REAL_OPENSSL_MAJOR >= 3
1382
+ EVP_MAC* mac_;
1383
+ EVP_MAC_CTX* mac_ctx_;
1384
+ #else
1292
1385
  EVP_MD_CTX* md_ctx_;
1293
1386
  EVP_PKEY* polykey_;
1294
1387
  EVP_PKEY_CTX* polykey_ctx_;
1388
+ #endif
1295
1389
  };
1296
1390
 
1297
1391
  class AESGCMDecipher : public ObjectWrap {
@@ -1320,7 +1414,6 @@ class AESGCMDecipher : public ObjectWrap {
1320
1414
 
1321
1415
  void clear() {
1322
1416
  if (ctx_) {
1323
- EVP_CIPHER_CTX_cleanup(ctx_);
1324
1417
  EVP_CIPHER_CTX_free(ctx_);
1325
1418
  ctx_ = nullptr;
1326
1419
  }
@@ -1355,12 +1448,12 @@ class AESGCMDecipher : public ObjectWrap {
1355
1448
  goto out;
1356
1449
  }
1357
1450
 
1358
- //~ if (iv_len != static_cast<size_t>(EVP_CIPHER_CTX_iv_length(ctx_))) {
1451
+ //~ if (iv_len != static_cast<size_t>(ctx_iv_len(ctx_))) {
1359
1452
  //~ r = kErrBadIVLen;
1360
1453
  //~ goto out;
1361
1454
  //~ }
1362
1455
 
1363
- if (key_len != static_cast<size_t>(EVP_CIPHER_CTX_key_length(ctx_))) {
1456
+ if (key_len != static_cast<size_t>(ctx_key_len(ctx_))) {
1364
1457
  if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) {
1365
1458
  r = kErrBadKeyLen;
1366
1459
  goto out;
@@ -1578,7 +1671,6 @@ class GenericDecipher : public ObjectWrap {
1578
1671
 
1579
1672
  void clear() {
1580
1673
  if (ctx_) {
1581
- EVP_CIPHER_CTX_cleanup(ctx_);
1582
1674
  EVP_CIPHER_CTX_free(ctx_);
1583
1675
  ctx_ = nullptr;
1584
1676
  }
@@ -1613,12 +1705,12 @@ class GenericDecipher : public ObjectWrap {
1613
1705
  goto out;
1614
1706
  }
1615
1707
 
1616
- if (iv_len != static_cast<size_t>(EVP_CIPHER_CTX_iv_length(ctx_))) {
1708
+ if (iv_len != static_cast<size_t>(ctx_iv_len(ctx_))) {
1617
1709
  r = kErrBadIVLen;
1618
1710
  goto out;
1619
1711
  }
1620
1712
 
1621
- if (key_len != static_cast<size_t>(EVP_CIPHER_CTX_key_length(ctx_))) {
1713
+ if (key_len != static_cast<size_t>(ctx_key_len(ctx_))) {
1622
1714
  if (!EVP_CIPHER_CTX_set_key_length(ctx_, key_len)) {
1623
1715
  r = kErrBadKeyLen;
1624
1716
  goto out;
@@ -1673,7 +1765,11 @@ class GenericDecipher : public ObjectWrap {
1673
1765
  hmac_len_ = HMAC_size(ctx_hmac_);
1674
1766
  hmac_actual_len_ = hmac_actual_len;
1675
1767
  is_etm_ = is_etm;
1768
+ #if REAL_OPENSSL_MAJOR >= 3
1676
1769
  switch (EVP_CIPHER_CTX_mode(ctx_)) {
1770
+ #else
1771
+ switch (cipher_flags(EVP_CIPHER_CTX_cipher(ctx_)) & EVP_CIPH_MODE) {
1772
+ #endif
1677
1773
  case EVP_CIPH_STREAM_CIPHER:
1678
1774
  case EVP_CIPH_CTR_MODE:
1679
1775
  is_stream_ = 1;
@@ -1681,25 +1777,17 @@ class GenericDecipher : public ObjectWrap {
1681
1777
  default:
1682
1778
  is_stream_ = 0;
1683
1779
  }
1684
- block_size_ = EVP_CIPHER_CTX_block_size(ctx_);
1780
+ block_size_ = ctx_get_block_size(ctx_);
1685
1781
 
1686
1782
  out:
1687
1783
  return r;
1688
1784
  }
1689
1785
 
1690
- ErrorType decrypt_block(unsigned char* data,
1691
- uint32_t data_len,
1692
- uint32_t seqno) {
1786
+ ErrorType decrypt_block(unsigned char* data, uint32_t data_len) {
1693
1787
  ErrorType r = kErrNone;
1694
1788
 
1695
1789
  int outlen;
1696
1790
 
1697
- uint8_t seqbuf[4] = {0};
1698
- ((uint8_t*)(seqbuf))[0] = (seqno >> 24) & 0xff;
1699
- ((uint8_t*)(seqbuf))[1] = (seqno >> 16) & 0xff;
1700
- ((uint8_t*)(seqbuf))[2] = (seqno >> 8) & 0xff;
1701
- ((uint8_t*)(seqbuf))[3] = seqno & 0xff;
1702
-
1703
1791
  if (!is_stream_ && data_len != block_size_) {
1704
1792
  r = kErrBadBlockLen;
1705
1793
  goto out;
@@ -1749,7 +1837,7 @@ out:
1749
1837
  unsigned int outlen = hmac_len_;
1750
1838
  if (HMAC_Init_ex(ctx_hmac_, nullptr, 0, nullptr, nullptr) != 1
1751
1839
  || HMAC_Update(ctx_hmac_, seqbuf, sizeof(seqbuf)) != 1
1752
- || HMAC_Update(ctx_hmac_, first_block, first_block_len) != 1
1840
+ || HMAC_Update(ctx_hmac_, first_block, 4) != 1
1753
1841
  || HMAC_Update(ctx_hmac_, packet, packet_len) != 1
1754
1842
  || HMAC_Final(ctx_hmac_, calc_mac, &outlen) != 1) {
1755
1843
  r = kErrOpenSSL;
@@ -1804,7 +1892,7 @@ out:
1804
1892
  unsigned int outlen = hmac_len_;
1805
1893
  if (HMAC_Init_ex(ctx_hmac_, nullptr, 0, nullptr, nullptr) != 1
1806
1894
  || HMAC_Update(ctx_hmac_, seqbuf, sizeof(seqbuf)) != 1
1807
- || HMAC_Update(ctx_hmac_, first_block, first_block_len) != 1
1895
+ || HMAC_Update(ctx_hmac_, first_block, 4) != 1
1808
1896
  || HMAC_Update(ctx_hmac_, packet, packet_len) != 1
1809
1897
  || HMAC_Final(ctx_hmac_, calc_mac, &outlen) != 1) {
1810
1898
  r = kErrOpenSSL;
@@ -1908,13 +1996,9 @@ out:
1908
1996
  if (!Buffer::HasInstance(info[0]))
1909
1997
  return Nan::ThrowTypeError("Missing/Invalid block");
1910
1998
 
1911
- if (!info[1]->IsUint32())
1912
- return Nan::ThrowTypeError("Missing/Invalid sequence number");
1913
-
1914
1999
  ErrorType r = obj->decrypt_block(
1915
2000
  reinterpret_cast<unsigned char*>(Buffer::Data(info[0])),
1916
- Buffer::Length(info[0]),
1917
- Nan::To<uint32_t>(info[1]).FromJust()
2001
+ Buffer::Length(info[0])
1918
2002
  );
1919
2003
  switch (r) {
1920
2004
  case kErrNone:
@@ -1969,6 +2053,8 @@ out:
1969
2053
  return Nan::ThrowError("Failed to completely decrypt packet");
1970
2054
  case kErrBadHMACLen:
1971
2055
  return Nan::ThrowError("Unexpected HMAC length");
2056
+ case kErrInvalidMAC:
2057
+ return Nan::ThrowError("Invalid MAC");
1972
2058
  case kErrOpenSSL: {
1973
2059
  char msg_buf[128] = {0};
1974
2060
  ERR_error_string_n(ERR_get_error(), msg_buf, sizeof(msg_buf));
@@ -2001,6 +2087,55 @@ out:
2001
2087
 
2002
2088
 
2003
2089
  NAN_MODULE_INIT(init) {
2090
+ // These are needed because node-gyp (as of this writing) does not use the
2091
+ // proper (OpenSSL) system headers when node was built against a shared
2092
+ // version of OpenSSL. Usually this isn't an issue because OSes that build
2093
+ // node in this way typically use the same version of OpenSSL as was bundled
2094
+ // with node for a particular node version for the best compatibility. However
2095
+ // with the inclusion of OpenSSL 3.x in node v17.x, some OSes are still
2096
+ // linking with a shared OpenSSL 1.x, which can cause both compilation and
2097
+ // runtime errors because of changes in OpenSSL's code.
2098
+ //
2099
+ // For that reason, we need to make sure we need to resolve some specific
2100
+ // symbols at runtime to workaround these buggy situations.
2101
+ #ifdef _WIN32
2102
+ # define load_sym(name) GetProcAddress(GetModuleHandle(NULL), name)
2103
+ #else
2104
+ # define load_sym(name) dlsym(RTLD_DEFAULT, name)
2105
+ #endif
2106
+ ctx_iv_len = reinterpret_cast<ctx_iv_len_func>(
2107
+ load_sym("EVP_CIPHER_CTX_get_iv_length")
2108
+ );
2109
+ if (!ctx_iv_len) {
2110
+ ctx_iv_len = reinterpret_cast<ctx_iv_len_func>(
2111
+ load_sym("EVP_CIPHER_CTX_iv_length")
2112
+ );
2113
+ }
2114
+ ctx_key_len = reinterpret_cast<ctx_key_len_func>(
2115
+ load_sym("EVP_CIPHER_CTX_get_key_length")
2116
+ );
2117
+ if (!ctx_key_len) {
2118
+ ctx_key_len = reinterpret_cast<ctx_key_len_func>(
2119
+ load_sym("EVP_CIPHER_CTX_key_length")
2120
+ );
2121
+ }
2122
+ cipher_flags = reinterpret_cast<cipher_flags_func>(
2123
+ load_sym("EVP_CIPHER_get_flags")
2124
+ );
2125
+ if (!cipher_flags) {
2126
+ cipher_flags = reinterpret_cast<cipher_flags_func>(
2127
+ load_sym("EVP_CIPHER_flags")
2128
+ );
2129
+ }
2130
+ ctx_get_block_size = reinterpret_cast<ctx_get_block_size_func>(
2131
+ load_sym("EVP_CIPHER_CTX_get_block_size")
2132
+ );
2133
+ if (!ctx_get_block_size) {
2134
+ ctx_get_block_size = reinterpret_cast<ctx_get_block_size_func>(
2135
+ load_sym("EVP_CIPHER_CTX_block_size")
2136
+ );
2137
+ }
2138
+
2004
2139
  ChaChaPolyCipher::Init(target);
2005
2140
  AESGCMCipher::Init(target);
2006
2141
  GenericCipher::Init(target);
@@ -576,22 +576,17 @@ class NullDecipher {
576
576
  // Read padding length, payload, and padding
577
577
  if (this._packetPos < this._len) {
578
578
  const nb = Math.min(this._len - this._packetPos, dataLen - p);
579
- if (p !== 0 || nb !== dataLen) {
580
- if (nb === this._len) {
581
- this._packet = new FastBuffer(data.buffer, data.byteOffset + p, nb);
582
- } else {
583
- this._packet = Buffer.allocUnsafe(this._len);
584
- this._packet.set(
585
- new Uint8Array(data.buffer, data.byteOffset + p, nb),
586
- this._packetPos
587
- );
588
- }
589
- } else if (nb === this._len) {
590
- this._packet = data;
579
+ let chunk;
580
+ if (p !== 0 || nb !== dataLen)
581
+ chunk = new Uint8Array(data.buffer, data.byteOffset + p, nb);
582
+ else
583
+ chunk = data;
584
+ if (nb === this._len) {
585
+ this._packet = chunk;
591
586
  } else {
592
587
  if (!this._packet)
593
588
  this._packet = Buffer.allocUnsafe(this._len);
594
- this._packet.set(data, this._packetPos);
589
+ this._packet.set(chunk, this._packetPos);
595
590
  }
596
591
  p += nb;
597
592
  this._packetPos += nb;
@@ -1334,7 +1329,7 @@ class GenericDecipherBinding {
1334
1329
  this._len = need = readUInt32BE(this._block, 0);
1335
1330
  } else {
1336
1331
  // Decrypt first block to get packet length
1337
- this._instance.decryptBlock(this._block, this.inSeqno);
1332
+ this._instance.decryptBlock(this._block);
1338
1333
  this._len = readUInt32BE(this._block, 0);
1339
1334
  need = 4 + this._len - this._block.length;
1340
1335
  }
@@ -1346,35 +1341,15 @@ class GenericDecipherBinding {
1346
1341
  }
1347
1342
 
1348
1343
  if (!this._macETM) {
1349
- const pktStart = (this._block.length - 4);
1350
- const startP = p - pktStart;
1351
- let endP;
1352
- if (p >= pktStart && (endP = startP + this._len) <= dataLen) {
1353
- // The entire packet exists within the current chunk, with the
1354
- // first block already decrypted
1355
- if (startP === 0 && endP === dataLen) {
1356
- this._packet = data;
1357
- this._pktLen = this._len;
1358
- } else {
1359
- this._packet = new FastBuffer(
1360
- data.buffer,
1361
- data.byteOffset + startP,
1362
- this._len
1363
- );
1364
- this._pktLen = this._len;
1365
- }
1366
- p = endP;
1367
- } else {
1368
- this._pktLen = pktStart;
1369
- if (this._pktLen) {
1370
- this._packet = Buffer.allocUnsafe(this._len);
1371
- this._packet.set(
1372
- new Uint8Array(this._block.buffer,
1373
- this._block.byteOffset + 4,
1374
- this._pktLen),
1375
- 0
1376
- );
1377
- }
1344
+ this._pktLen = (this._block.length - 4);
1345
+ if (this._pktLen) {
1346
+ this._packet = Buffer.allocUnsafe(this._len);
1347
+ this._packet.set(
1348
+ new Uint8Array(this._block.buffer,
1349
+ this._block.byteOffset + 4,
1350
+ this._pktLen),
1351
+ 0
1352
+ );
1378
1353
  }
1379
1354
  }
1380
1355
 
@@ -79,7 +79,7 @@ function kexinit(self) {
79
79
  let kex = entry.array;
80
80
  let found = false;
81
81
  for (let i = 0; i < kex.length; ++i) {
82
- if (kex[i].indexOf('group-exchange') !== -1) {
82
+ if (kex[i].includes('group-exchange')) {
83
83
  if (!found) {
84
84
  found = true;
85
85
  // Copy array lazily
@@ -101,9 +101,9 @@ function kexinit(self) {
101
101
  );
102
102
 
103
103
  payload = Buffer.allocUnsafe(len);
104
- writeUInt32BE(payload, newKexBuf.length, 0);
105
- payload.set(newKexBuf, 4);
106
- payload.set(rest, 4 + newKexBuf.length);
104
+ writeUInt32BE(payload, newKexBuf.length, 17);
105
+ payload.set(newKexBuf, 17 + 4);
106
+ payload.set(rest, 17 + 4 + newKexBuf.length);
107
107
  }
108
108
  }
109
109
 
@@ -778,18 +778,7 @@ const createKeyExchange = (() => {
778
778
  this._protocol._packetRW.write.finalize(packet, true)
779
779
  );
780
780
  }
781
- if (!this._sentNEWKEYS) {
782
- this._protocol._debug && this._protocol._debug(
783
- 'Outbound: Sending NEWKEYS'
784
- );
785
- const p = this._protocol._packetRW.write.allocStartKEX;
786
- const packet = this._protocol._packetRW.write.alloc(1, true);
787
- packet[p] = MESSAGE.NEWKEYS;
788
- this._protocol._cipher.encrypt(
789
- this._protocol._packetRW.write.finalize(packet, true)
790
- );
791
- this._sentNEWKEYS = true;
792
- }
781
+ trySendNEWKEYS(this);
793
782
 
794
783
  const completeHandshake = () => {
795
784
  if (!this.sessionID)
@@ -1180,6 +1169,8 @@ const createKeyExchange = (() => {
1180
1169
  this._hostVerified = true;
1181
1170
  if (this._receivedNEWKEYS)
1182
1171
  this.finish();
1172
+ else
1173
+ trySendNEWKEYS(this);
1183
1174
  });
1184
1175
  }
1185
1176
  if (ret === undefined) {
@@ -1203,6 +1194,7 @@ const createKeyExchange = (() => {
1203
1194
  'Host accepted (verified)'
1204
1195
  );
1205
1196
  this._hostVerified = true;
1197
+ trySendNEWKEYS(this);
1206
1198
  }
1207
1199
  ++this._step;
1208
1200
  break;
@@ -1798,6 +1790,21 @@ function dhEstimate(neg) {
1798
1790
  return 8192;
1799
1791
  }
1800
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
+
1801
1808
  module.exports = {
1802
1809
  KexInit,
1803
1810
  kexinit,
@@ -595,6 +595,8 @@ OpenSSH_Private.prototype = BaseKey;
595
595
  } else {
596
596
  ret = [];
597
597
  }
598
+ if (ret instanceof Error)
599
+ return ret;
598
600
  // This will need to change if/when OpenSSH ever starts storing multiple
599
601
  // keys in their key files
600
602
  return ret[0];
@@ -171,7 +171,7 @@ module.exports = {
171
171
  doFatalError: (protocol, msg, level, reason) => {
172
172
  let err;
173
173
  if (DISCONNECT_REASON === undefined)
174
- ({ DISCONNECT_REASON } = require('./utils.js'));
174
+ ({ DISCONNECT_REASON } = require('./constants.js'));
175
175
  if (msg instanceof Error) {
176
176
  // doFatalError(protocol, err[, reason])
177
177
  err = msg;
package/lib/server.js CHANGED
@@ -189,6 +189,7 @@ class Session extends EventEmitter {
189
189
 
190
190
  this.type = 'session';
191
191
  this.subtype = undefined;
192
+ this.server = true;
192
193
  this._ending = false;
193
194
  this._channel = undefined;
194
195
  this._chanInfo = {
@@ -351,6 +352,10 @@ class Server extends EventEmitter {
351
352
  this.maxConnections = Infinity;
352
353
  }
353
354
 
355
+ injectSocket(socket) {
356
+ this._srv.emit('connection', socket);
357
+ }
358
+
354
359
  listen(...args) {
355
360
  this._srv.listen(...args);
356
361
  return this;
@@ -552,6 +557,8 @@ class Client extends EventEmitter {
552
557
  reason = CHANNEL_OPEN_FAILURE.CONNECT_FAILED;
553
558
  }
554
559
 
560
+ if (localChan !== -1)
561
+ this._chanMgr.remove(localChan);
555
562
  proto.channelOpenFail(info.sender, reason, '');
556
563
  };
557
564
  const reserveChannel = () => {
@@ -1194,6 +1201,7 @@ class Client extends EventEmitter {
1194
1201
 
1195
1202
  socket.pause();
1196
1203
  cryptoInit.then(() => {
1204
+ proto.start();
1197
1205
  socket.on('data', (data) => {
1198
1206
  try {
1199
1207
  proto.parse(data, 0, data.length);
package/lib/utils.js CHANGED
@@ -32,11 +32,17 @@ function onCHANNEL_CLOSE(self, recipient, channel, err, dead) {
32
32
  onChannelOpenFailure(self, recipient, err, channel);
33
33
  return;
34
34
  }
35
- if (typeof channel !== 'object'
36
- || channel === null
37
- || channel.incoming.state === 'closed') {
35
+
36
+ if (typeof channel !== 'object' || channel === null)
37
+ return;
38
+
39
+ if (channel.incoming && channel.incoming.state === 'closed')
40
+ return;
41
+
42
+ self._chanMgr.remove(recipient);
43
+
44
+ if (channel.server && channel.constructor.name === 'Session')
38
45
  return;
39
- }
40
46
 
41
47
  channel.incoming.state = 'closed';
42
48
 
@@ -58,8 +64,6 @@ function onCHANNEL_CLOSE(self, recipient, channel, err, dead) {
58
64
  if (channel.outgoing.state === 'closing')
59
65
  channel.outgoing.state = 'closed';
60
66
 
61
- self._chanMgr.remove(recipient);
62
-
63
67
  const readState = channel._readableState;
64
68
  const writeState = channel._writableState;
65
69
  if (writeState && !writeState.ending && !writeState.finished && !dead)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electerm/ssh2",
3
- "version": "1.2.1",
3
+ "version": "1.9.0",
4
4
  "author": "Brian White <mscdex@mscdex.net>",
5
5
  "description": "SSH2 client and server modules written in pure JavaScript for node.js",
6
6
  "main": "./lib/index.js",
@@ -15,10 +15,6 @@
15
15
  "@mscdex/eslint-config": "^1.0.0",
16
16
  "eslint": "^7.0.0"
17
17
  },
18
- "peerDependencies": {
19
- "cpu-features": "0.0.2",
20
- "nan": "^2.14.2"
21
- },
22
18
  "scripts": {
23
19
  "install": "node install.js",
24
20
  "rebuild": "node install.js",