@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 +12 -5
- package/lib/Channel.js +1 -0
- package/lib/client.js +1 -0
- package/lib/protocol/Protocol.js +9 -5
- package/lib/protocol/SFTP.js +185 -13
- package/lib/protocol/constants.js +16 -7
- package/lib/protocol/crypto/binding.gyp +9 -0
- package/lib/protocol/crypto/src/binding.cc +175 -40
- package/lib/protocol/crypto.js +18 -43
- package/lib/protocol/kex.js +23 -16
- package/lib/protocol/keyParser.js +2 -0
- package/lib/protocol/utils.js +1 -1
- package/lib/server.js +8 -0
- package/lib/utils.js +10 -6
- package/package.json +1 -5
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
|
|
8
|
+
const args = [
|
|
7
9
|
`--target=${process.version}`,
|
|
8
|
-
|
|
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
|
-
|
|
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
package/lib/client.js
CHANGED
package/lib/protocol/Protocol.js
CHANGED
|
@@ -260,14 +260,15 @@ class Protocol {
|
|
|
260
260
|
this._debug('Custom crypto binding not available');
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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);
|
package/lib/protocol/SFTP.js
CHANGED
|
@@ -154,7 +154,14 @@ class SFTP extends EventEmitter {
|
|
|
154
154
|
this._pktData = undefined;
|
|
155
155
|
this._writeReqid = -1;
|
|
156
156
|
this._requests = {};
|
|
157
|
-
this.
|
|
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.
|
|
212
|
-
const max = this.
|
|
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.
|
|
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 (
|
|
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 (
|
|
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.
|
|
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
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
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
|
|
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 (
|
|
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
|
-
|
|
250
|
-
|
|
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>(
|
|
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>(
|
|
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>(
|
|
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>(
|
|
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 (
|
|
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>(
|
|
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>(
|
|
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>(
|
|
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>(
|
|
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_ =
|
|
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,
|
|
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,
|
|
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);
|
package/lib/protocol/crypto.js
CHANGED
|
@@ -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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
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
|
|
package/lib/protocol/kex.js
CHANGED
|
@@ -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].
|
|
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,
|
|
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
|
-
|
|
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,
|
package/lib/protocol/utils.js
CHANGED
|
@@ -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('./
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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.
|
|
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",
|