@electerm/ssh2 1.11.1 → 1.14.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/lib/client.js +137 -8
- package/lib/index.js +1 -0
- package/lib/keygen.js +582 -0
- package/lib/protocol/Protocol.js +74 -14
- package/lib/protocol/SFTP.js +189 -1
- package/lib/protocol/constants.js +5 -2
- package/lib/protocol/handlers.misc.js +76 -5
- package/lib/protocol/kex.js +57 -19
- package/lib/protocol/keyParser.js +6 -5
- package/lib/server.js +9 -0
- package/package.json +4 -4
package/lib/protocol/Protocol.js
CHANGED
|
@@ -44,12 +44,14 @@ const { bindingAvailable, NullCipher, NullDecipher } = require('./crypto.js');
|
|
|
44
44
|
const {
|
|
45
45
|
COMPAT_CHECKS,
|
|
46
46
|
DISCONNECT_REASON,
|
|
47
|
+
eddsaSupported,
|
|
47
48
|
MESSAGE,
|
|
48
49
|
SIGNALS,
|
|
49
50
|
TERMINAL_MODE,
|
|
50
51
|
} = require('./constants.js');
|
|
51
52
|
const {
|
|
52
|
-
|
|
53
|
+
DEFAULT_KEXINIT_CLIENT,
|
|
54
|
+
DEFAULT_KEXINIT_SERVER,
|
|
53
55
|
KexInit,
|
|
54
56
|
kexinit,
|
|
55
57
|
onKEXPayload,
|
|
@@ -138,8 +140,13 @@ class Protocol {
|
|
|
138
140
|
let onHandshakeComplete = config.onHandshakeComplete;
|
|
139
141
|
if (typeof onHandshakeComplete !== 'function')
|
|
140
142
|
onHandshakeComplete = noop;
|
|
143
|
+
let firstHandshake;
|
|
141
144
|
this._onHandshakeComplete = (...args) => {
|
|
142
145
|
this._debug && this._debug('Handshake completed');
|
|
146
|
+
if (firstHandshake === undefined)
|
|
147
|
+
firstHandshake = true;
|
|
148
|
+
else
|
|
149
|
+
firstHandshake = false;
|
|
143
150
|
|
|
144
151
|
// Process packets queued during a rekey where necessary
|
|
145
152
|
const oldQueue = this._queue;
|
|
@@ -165,6 +172,9 @@ class Protocol {
|
|
|
165
172
|
this._debug && this._debug('... finished draining outbound queue');
|
|
166
173
|
}
|
|
167
174
|
|
|
175
|
+
if (firstHandshake && this._server && this._kex.remoteExtInfoEnabled)
|
|
176
|
+
sendExtInfo(this);
|
|
177
|
+
|
|
168
178
|
onHandshakeComplete(...args);
|
|
169
179
|
};
|
|
170
180
|
this._queue = undefined;
|
|
@@ -205,10 +215,13 @@ class Protocol {
|
|
|
205
215
|
}
|
|
206
216
|
|
|
207
217
|
let offer = config.offer;
|
|
208
|
-
if (typeof offer !== 'object' || offer === null)
|
|
209
|
-
offer =
|
|
210
|
-
else if (offer.constructor !== KexInit)
|
|
218
|
+
if (typeof offer !== 'object' || offer === null) {
|
|
219
|
+
offer = (this._server ? DEFAULT_KEXINIT_SERVER : DEFAULT_KEXINIT_CLIENT);
|
|
220
|
+
} else if (offer.constructor !== KexInit) {
|
|
221
|
+
if (!this._server)
|
|
222
|
+
offer.kex = offer.kex.concat(['ext-info-c']);
|
|
211
223
|
offer = new KexInit(offer);
|
|
224
|
+
}
|
|
212
225
|
this._kex = undefined;
|
|
213
226
|
this._kexinit = undefined;
|
|
214
227
|
this._offer = offer;
|
|
@@ -608,7 +621,7 @@ class Protocol {
|
|
|
608
621
|
|
|
609
622
|
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
610
623
|
}
|
|
611
|
-
authPK(username, pubKey, cbSign) {
|
|
624
|
+
authPK(username, pubKey, keyAlgo, cbSign) {
|
|
612
625
|
if (this._server)
|
|
613
626
|
throw new Error('Client-only method called in server mode');
|
|
614
627
|
|
|
@@ -616,11 +629,26 @@ class Protocol {
|
|
|
616
629
|
if (pubKey instanceof Error)
|
|
617
630
|
throw new Error('Invalid key');
|
|
618
631
|
|
|
619
|
-
|
|
632
|
+
let keyType = pubKey.type;
|
|
633
|
+
if (keyType === 'ssh-rsa') {
|
|
634
|
+
for (const algo of ['rsa-sha2-512', 'rsa-sha2-256']) {
|
|
635
|
+
if (this._remoteHostKeyAlgorithms.includes(algo)) {
|
|
636
|
+
keyType = algo;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
620
641
|
pubKey = pubKey.getPublicSSH();
|
|
621
642
|
|
|
643
|
+
if (typeof keyAlgo === 'function') {
|
|
644
|
+
cbSign = keyAlgo;
|
|
645
|
+
keyAlgo = undefined;
|
|
646
|
+
}
|
|
647
|
+
if (!keyAlgo)
|
|
648
|
+
keyAlgo = keyType;
|
|
649
|
+
|
|
622
650
|
const userLen = Buffer.byteLength(username);
|
|
623
|
-
const algoLen = Buffer.byteLength(
|
|
651
|
+
const algoLen = Buffer.byteLength(keyAlgo);
|
|
624
652
|
const pubKeyLen = pubKey.length;
|
|
625
653
|
const sessionID = this._kex.sessionID;
|
|
626
654
|
const sesLen = sessionID.length;
|
|
@@ -654,7 +682,7 @@ class Protocol {
|
|
|
654
682
|
packet[p += 9] = (cbSign ? 1 : 0);
|
|
655
683
|
|
|
656
684
|
writeUInt32BE(packet, algoLen, ++p);
|
|
657
|
-
packet.utf8Write(
|
|
685
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
658
686
|
|
|
659
687
|
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
660
688
|
packet.set(pubKey, p += 4);
|
|
@@ -697,7 +725,7 @@ class Protocol {
|
|
|
697
725
|
packet[p += 9] = 1;
|
|
698
726
|
|
|
699
727
|
writeUInt32BE(packet, algoLen, ++p);
|
|
700
|
-
packet.utf8Write(
|
|
728
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
701
729
|
|
|
702
730
|
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
703
731
|
packet.set(pubKey, p += 4);
|
|
@@ -705,7 +733,7 @@ class Protocol {
|
|
|
705
733
|
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += pubKeyLen);
|
|
706
734
|
|
|
707
735
|
writeUInt32BE(packet, algoLen, p += 4);
|
|
708
|
-
packet.utf8Write(
|
|
736
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
709
737
|
|
|
710
738
|
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
711
739
|
packet.set(signature, p += 4);
|
|
@@ -720,7 +748,7 @@ class Protocol {
|
|
|
720
748
|
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
721
749
|
});
|
|
722
750
|
}
|
|
723
|
-
authHostbased(username, pubKey, hostname, userlocal, cbSign) {
|
|
751
|
+
authHostbased(username, pubKey, hostname, userlocal, keyAlgo, cbSign) {
|
|
724
752
|
// TODO: Make DRY by sharing similar code with authPK()
|
|
725
753
|
if (this._server)
|
|
726
754
|
throw new Error('Client-only method called in server mode');
|
|
@@ -732,8 +760,15 @@ class Protocol {
|
|
|
732
760
|
const keyType = pubKey.type;
|
|
733
761
|
pubKey = pubKey.getPublicSSH();
|
|
734
762
|
|
|
763
|
+
if (typeof keyAlgo === 'function') {
|
|
764
|
+
cbSign = keyAlgo;
|
|
765
|
+
keyAlgo = undefined;
|
|
766
|
+
}
|
|
767
|
+
if (!keyAlgo)
|
|
768
|
+
keyAlgo = keyType;
|
|
769
|
+
|
|
735
770
|
const userLen = Buffer.byteLength(username);
|
|
736
|
-
const algoLen = Buffer.byteLength(
|
|
771
|
+
const algoLen = Buffer.byteLength(keyAlgo);
|
|
737
772
|
const pubKeyLen = pubKey.length;
|
|
738
773
|
const sessionID = this._kex.sessionID;
|
|
739
774
|
const sesLen = sessionID.length;
|
|
@@ -760,7 +795,7 @@ class Protocol {
|
|
|
760
795
|
data.utf8Write('hostbased', p += 4, 9);
|
|
761
796
|
|
|
762
797
|
writeUInt32BE(data, algoLen, p += 9);
|
|
763
|
-
data.utf8Write(
|
|
798
|
+
data.utf8Write(keyAlgo, p += 4, algoLen);
|
|
764
799
|
|
|
765
800
|
writeUInt32BE(data, pubKeyLen, p += algoLen);
|
|
766
801
|
data.set(pubKey, p += 4);
|
|
@@ -787,7 +822,7 @@ class Protocol {
|
|
|
787
822
|
|
|
788
823
|
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += reqDataLen);
|
|
789
824
|
writeUInt32BE(packet, algoLen, p += 4);
|
|
790
|
-
packet.utf8Write(
|
|
825
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
791
826
|
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
792
827
|
packet.set(signature, p += 4);
|
|
793
828
|
|
|
@@ -2074,4 +2109,29 @@ function modesToBytes(modes) {
|
|
|
2074
2109
|
return bytes;
|
|
2075
2110
|
}
|
|
2076
2111
|
|
|
2112
|
+
function sendExtInfo(proto) {
|
|
2113
|
+
let serverSigAlgs =
|
|
2114
|
+
'ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521'
|
|
2115
|
+
+ 'rsa-sha2-512,rsa-sha2-256,ssh-rsa,ssh-dss';
|
|
2116
|
+
if (eddsaSupported)
|
|
2117
|
+
serverSigAlgs = `ssh-ed25519,${serverSigAlgs}`;
|
|
2118
|
+
const algsLen = Buffer.byteLength(serverSigAlgs);
|
|
2119
|
+
|
|
2120
|
+
let p = proto._packetRW.write.allocStart;
|
|
2121
|
+
const packet = proto._packetRW.write.alloc(1 + 4 + 4 + 15 + 4 + algsLen);
|
|
2122
|
+
|
|
2123
|
+
packet[p] = MESSAGE.EXT_INFO;
|
|
2124
|
+
|
|
2125
|
+
writeUInt32BE(packet, 1, ++p);
|
|
2126
|
+
|
|
2127
|
+
writeUInt32BE(packet, 15, p += 4);
|
|
2128
|
+
packet.utf8Write('server-sig-algs', p += 4, 15);
|
|
2129
|
+
|
|
2130
|
+
writeUInt32BE(packet, algsLen, p += 15);
|
|
2131
|
+
packet.utf8Write(serverSigAlgs, p += 4, algsLen);
|
|
2132
|
+
|
|
2133
|
+
proto._debug && proto._debug('Outbound: Sending EXT_INFO');
|
|
2134
|
+
sendPacket(proto, proto._packetRW.write.finalize(packet));
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2077
2137
|
module.exports = Protocol;
|
package/lib/protocol/SFTP.js
CHANGED
|
@@ -1588,7 +1588,17 @@ class SFTP extends EventEmitter {
|
|
|
1588
1588
|
writeUInt32BE(buf, pathLen, p += 20);
|
|
1589
1589
|
buf.utf8Write(path, p += 4, pathLen);
|
|
1590
1590
|
|
|
1591
|
-
this._requests[reqid] = {
|
|
1591
|
+
this._requests[reqid] = {
|
|
1592
|
+
cb: (err, names) => {
|
|
1593
|
+
if (typeof cb !== 'function')
|
|
1594
|
+
return;
|
|
1595
|
+
if (err)
|
|
1596
|
+
return cb(err);
|
|
1597
|
+
if (!names || !names.length)
|
|
1598
|
+
return cb(new Error('Response missing expanded path'));
|
|
1599
|
+
cb(undefined, names[0].filename);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1592
1602
|
|
|
1593
1603
|
const isBuffered = sendOrBuffer(this, buf);
|
|
1594
1604
|
if (this._debug) {
|
|
@@ -1681,6 +1691,146 @@ class SFTP extends EventEmitter {
|
|
|
1681
1691
|
this._debug(`SFTP: Outbound: ${status} copy-data`);
|
|
1682
1692
|
}
|
|
1683
1693
|
}
|
|
1694
|
+
ext_home_dir(username, cb) {
|
|
1695
|
+
if (this.server)
|
|
1696
|
+
throw new Error('Client-only method called in server mode');
|
|
1697
|
+
|
|
1698
|
+
const ext = this._extensions['home-directory'];
|
|
1699
|
+
if (ext !== '1')
|
|
1700
|
+
throw new Error('Server does not support this extended request');
|
|
1701
|
+
|
|
1702
|
+
if (typeof username !== 'string')
|
|
1703
|
+
throw new TypeError('username is not a string');
|
|
1704
|
+
|
|
1705
|
+
/*
|
|
1706
|
+
uint32 id
|
|
1707
|
+
string "home-directory"
|
|
1708
|
+
string username
|
|
1709
|
+
*/
|
|
1710
|
+
let p = 0;
|
|
1711
|
+
const usernameLen = Buffer.byteLength(username);
|
|
1712
|
+
const buf = Buffer.allocUnsafe(
|
|
1713
|
+
4 + 1
|
|
1714
|
+
+ 4
|
|
1715
|
+
+ 4 + 14
|
|
1716
|
+
+ 4 + usernameLen
|
|
1717
|
+
);
|
|
1718
|
+
|
|
1719
|
+
writeUInt32BE(buf, buf.length - 4, p);
|
|
1720
|
+
p += 4;
|
|
1721
|
+
|
|
1722
|
+
buf[p] = REQUEST.EXTENDED;
|
|
1723
|
+
++p;
|
|
1724
|
+
|
|
1725
|
+
const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
|
|
1726
|
+
writeUInt32BE(buf, reqid, p);
|
|
1727
|
+
p += 4;
|
|
1728
|
+
|
|
1729
|
+
writeUInt32BE(buf, 14, p);
|
|
1730
|
+
p += 4;
|
|
1731
|
+
buf.utf8Write('home-directory', p, 14);
|
|
1732
|
+
p += 14;
|
|
1733
|
+
|
|
1734
|
+
writeUInt32BE(buf, usernameLen, p);
|
|
1735
|
+
p += 4;
|
|
1736
|
+
buf.utf8Write(username, p, usernameLen);
|
|
1737
|
+
p += usernameLen;
|
|
1738
|
+
|
|
1739
|
+
this._requests[reqid] = {
|
|
1740
|
+
cb: (err, names) => {
|
|
1741
|
+
if (typeof cb !== 'function')
|
|
1742
|
+
return;
|
|
1743
|
+
if (err)
|
|
1744
|
+
return cb(err);
|
|
1745
|
+
if (!names || !names.length)
|
|
1746
|
+
return cb(new Error('Response missing home directory'));
|
|
1747
|
+
cb(undefined, names[0].filename);
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
|
|
1751
|
+
const isBuffered = sendOrBuffer(this, buf);
|
|
1752
|
+
if (this._debug) {
|
|
1753
|
+
const status = (isBuffered ? 'Buffered' : 'Sending');
|
|
1754
|
+
this._debug(`SFTP: Outbound: ${status} home-directory`);
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
ext_users_groups(uids, gids, cb) {
|
|
1758
|
+
if (this.server)
|
|
1759
|
+
throw new Error('Client-only method called in server mode');
|
|
1760
|
+
|
|
1761
|
+
const ext = this._extensions['users-groups-by-id@openssh.com'];
|
|
1762
|
+
if (ext !== '1')
|
|
1763
|
+
throw new Error('Server does not support this extended request');
|
|
1764
|
+
|
|
1765
|
+
if (!Array.isArray(uids))
|
|
1766
|
+
throw new TypeError('uids is not an array');
|
|
1767
|
+
for (const val of uids) {
|
|
1768
|
+
if (!Number.isInteger(val) || val < 0 || val > (2 ** 32 - 1))
|
|
1769
|
+
throw new Error('uid values must all be 32-bit unsigned integers');
|
|
1770
|
+
}
|
|
1771
|
+
if (!Array.isArray(gids))
|
|
1772
|
+
throw new TypeError('gids is not an array');
|
|
1773
|
+
for (const val of gids) {
|
|
1774
|
+
if (!Number.isInteger(val) || val < 0 || val > (2 ** 32 - 1))
|
|
1775
|
+
throw new Error('gid values must all be 32-bit unsigned integers');
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
/*
|
|
1779
|
+
uint32 id
|
|
1780
|
+
string "users-groups-by-id@openssh.com"
|
|
1781
|
+
string uids
|
|
1782
|
+
uint32 uid1
|
|
1783
|
+
...
|
|
1784
|
+
string gids
|
|
1785
|
+
uint32 gid1
|
|
1786
|
+
...
|
|
1787
|
+
*/
|
|
1788
|
+
let p = 0;
|
|
1789
|
+
const buf = Buffer.allocUnsafe(
|
|
1790
|
+
4 + 1
|
|
1791
|
+
+ 4
|
|
1792
|
+
+ 4 + 30
|
|
1793
|
+
+ 4 + (4 * uids.length)
|
|
1794
|
+
+ 4 + (4 * gids.length)
|
|
1795
|
+
);
|
|
1796
|
+
|
|
1797
|
+
writeUInt32BE(buf, buf.length - 4, p);
|
|
1798
|
+
p += 4;
|
|
1799
|
+
|
|
1800
|
+
buf[p] = REQUEST.EXTENDED;
|
|
1801
|
+
++p;
|
|
1802
|
+
|
|
1803
|
+
const reqid = this._writeReqid = (this._writeReqid + 1) & MAX_REQID;
|
|
1804
|
+
writeUInt32BE(buf, reqid, p);
|
|
1805
|
+
p += 4;
|
|
1806
|
+
|
|
1807
|
+
writeUInt32BE(buf, 30, p);
|
|
1808
|
+
p += 4;
|
|
1809
|
+
buf.utf8Write('users-groups-by-id@openssh.com', p, 30);
|
|
1810
|
+
p += 30;
|
|
1811
|
+
|
|
1812
|
+
writeUInt32BE(buf, 4 * uids.length, p);
|
|
1813
|
+
p += 4;
|
|
1814
|
+
for (const val of uids) {
|
|
1815
|
+
writeUInt32BE(buf, val, p);
|
|
1816
|
+
p += 4;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
writeUInt32BE(buf, 4 * gids.length, p);
|
|
1820
|
+
p += 4;
|
|
1821
|
+
for (const val of gids) {
|
|
1822
|
+
writeUInt32BE(buf, val, p);
|
|
1823
|
+
p += 4;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
this._requests[reqid] = { extended: 'users-groups-by-id@openssh.com', cb };
|
|
1827
|
+
|
|
1828
|
+
const isBuffered = sendOrBuffer(this, buf);
|
|
1829
|
+
if (this._debug) {
|
|
1830
|
+
const status = (isBuffered ? 'Buffered' : 'Sending');
|
|
1831
|
+
this._debug(`SFTP: Outbound: ${status} users-groups-by-id@openssh.com`);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1684
1834
|
// ===========================================================================
|
|
1685
1835
|
// Server-specific ===========================================================
|
|
1686
1836
|
// ===========================================================================
|
|
@@ -2928,6 +3078,44 @@ const CLIENT_HANDLERS = {
|
|
|
2928
3078
|
req.cb(undefined, limits);
|
|
2929
3079
|
return;
|
|
2930
3080
|
}
|
|
3081
|
+
case 'users-groups-by-id@openssh.com': {
|
|
3082
|
+
/*
|
|
3083
|
+
string usernames
|
|
3084
|
+
string username1
|
|
3085
|
+
...
|
|
3086
|
+
string groupnames
|
|
3087
|
+
string groupname1
|
|
3088
|
+
...
|
|
3089
|
+
*/
|
|
3090
|
+
const usernameCount = bufferParser.readUInt32BE();
|
|
3091
|
+
if (usernameCount === undefined)
|
|
3092
|
+
break;
|
|
3093
|
+
const usernames = new Array(usernameCount);
|
|
3094
|
+
for (let i = 0; i < usernames.length; ++i)
|
|
3095
|
+
usernames[i] = bufferParser.readString(true);
|
|
3096
|
+
|
|
3097
|
+
const groupnameCount = bufferParser.readUInt32BE();
|
|
3098
|
+
if (groupnameCount === undefined)
|
|
3099
|
+
break;
|
|
3100
|
+
const groupnames = new Array(groupnameCount);
|
|
3101
|
+
for (let i = 0; i < groupnames.length; ++i)
|
|
3102
|
+
groupnames[i] = bufferParser.readString(true);
|
|
3103
|
+
if (groupnames.length > 0
|
|
3104
|
+
&& groupnames[groupnames.length - 1] === undefined) {
|
|
3105
|
+
break;
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
if (sftp._debug) {
|
|
3109
|
+
sftp._debug(
|
|
3110
|
+
'SFTP: Inbound: Received EXTENDED_REPLY '
|
|
3111
|
+
+ `(id:${reqID}, ${req.extended})`
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
bufferParser.clear();
|
|
3115
|
+
if (typeof req.cb === 'function')
|
|
3116
|
+
req.cb(undefined, usernames, groupnames);
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
2931
3119
|
default:
|
|
2932
3120
|
// Unknown extended request
|
|
2933
3121
|
sftp._debug && sftp._debug(
|
|
@@ -159,6 +159,7 @@ const COMPAT = {
|
|
|
159
159
|
OLD_EXIT: 1 << 1,
|
|
160
160
|
DYN_RPORT_BUG: 1 << 2,
|
|
161
161
|
BUG_DHGEX_LARGE: 1 << 3,
|
|
162
|
+
IMPLY_RSA_SHA2_SIGALGS: 1 << 4,
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
module.exports = {
|
|
@@ -170,6 +171,7 @@ module.exports = {
|
|
|
170
171
|
DEBUG: 4,
|
|
171
172
|
SERVICE_REQUEST: 5,
|
|
172
173
|
SERVICE_ACCEPT: 6,
|
|
174
|
+
EXT_INFO: 7, // RFC 8308
|
|
173
175
|
|
|
174
176
|
// Transport layer protocol -- algorithm negotiation (20-29)
|
|
175
177
|
KEXINIT: 20,
|
|
@@ -327,9 +329,10 @@ module.exports = {
|
|
|
327
329
|
COMPAT,
|
|
328
330
|
COMPAT_CHECKS: [
|
|
329
331
|
[ 'Cisco-1.25', COMPAT.BAD_DHGEX ],
|
|
330
|
-
[ /^Cisco-1
|
|
332
|
+
[ /^Cisco-1[.]/, COMPAT.BUG_DHGEX_LARGE ],
|
|
331
333
|
[ /^[0-9.]+$/, COMPAT.OLD_EXIT ], // old SSH.com implementations
|
|
332
|
-
[ /^OpenSSH_5
|
|
334
|
+
[ /^OpenSSH_5[.][0-9]+/, COMPAT.DYN_RPORT_BUG ],
|
|
335
|
+
[ /^OpenSSH_7[.]4/, COMPAT.IMPLY_RSA_SHA2_SIGALGS ],
|
|
333
336
|
],
|
|
334
337
|
|
|
335
338
|
// KEX proposal-related
|
|
@@ -146,6 +146,48 @@ module.exports = {
|
|
|
146
146
|
const handler = self._handlers.SERVICE_ACCEPT;
|
|
147
147
|
handler && handler(self, name);
|
|
148
148
|
},
|
|
149
|
+
[MESSAGE.EXT_INFO]: (self, payload) => {
|
|
150
|
+
/*
|
|
151
|
+
byte SSH_MSG_EXT_INFO
|
|
152
|
+
uint32 nr-extensions
|
|
153
|
+
repeat the following 2 fields "nr-extensions" times:
|
|
154
|
+
string extension-name
|
|
155
|
+
string extension-value (binary)
|
|
156
|
+
*/
|
|
157
|
+
bufferParser.init(payload, 1);
|
|
158
|
+
const numExts = bufferParser.readUInt32BE();
|
|
159
|
+
let exts;
|
|
160
|
+
if (numExts !== undefined) {
|
|
161
|
+
exts = [];
|
|
162
|
+
for (let i = 0; i < numExts; ++i) {
|
|
163
|
+
const name = bufferParser.readString(true);
|
|
164
|
+
const data = bufferParser.readString();
|
|
165
|
+
if (data !== undefined) {
|
|
166
|
+
switch (name) {
|
|
167
|
+
case 'server-sig-algs': {
|
|
168
|
+
const algs = data.latin1Slice(0, data.length).split(',');
|
|
169
|
+
exts.push({ name, algs });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
default:
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Malformed
|
|
177
|
+
exts = undefined;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
bufferParser.clear();
|
|
182
|
+
|
|
183
|
+
if (exts === undefined)
|
|
184
|
+
return doFatalError(self, 'Inbound: Malformed EXT_INFO packet');
|
|
185
|
+
|
|
186
|
+
self._debug && self._debug('Inbound: Received EXT_INFO');
|
|
187
|
+
|
|
188
|
+
const handler = self._handlers.EXT_INFO;
|
|
189
|
+
handler && handler(self, exts);
|
|
190
|
+
},
|
|
149
191
|
|
|
150
192
|
// User auth protocol -- generic =============================================
|
|
151
193
|
[MESSAGE.USERAUTH_REQUEST]: (self, payload) => {
|
|
@@ -195,7 +237,21 @@ module.exports = {
|
|
|
195
237
|
const hasSig = bufferParser.readBool();
|
|
196
238
|
if (hasSig !== undefined) {
|
|
197
239
|
const keyAlgo = bufferParser.readString(true);
|
|
240
|
+
let realKeyAlgo = keyAlgo;
|
|
198
241
|
const key = bufferParser.readString();
|
|
242
|
+
|
|
243
|
+
let hashAlgo;
|
|
244
|
+
switch (keyAlgo) {
|
|
245
|
+
case 'rsa-sha2-256':
|
|
246
|
+
realKeyAlgo = 'ssh-rsa';
|
|
247
|
+
hashAlgo = 'sha256';
|
|
248
|
+
break;
|
|
249
|
+
case 'rsa-sha2-512':
|
|
250
|
+
realKeyAlgo = 'ssh-rsa';
|
|
251
|
+
hashAlgo = 'sha512';
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
199
255
|
if (hasSig) {
|
|
200
256
|
const blobEnd = bufferParser.pos();
|
|
201
257
|
let signature = bufferParser.readString();
|
|
@@ -206,7 +262,7 @@ module.exports = {
|
|
|
206
262
|
signature = bufferSlice(signature, 4 + keyAlgo.length + 4);
|
|
207
263
|
}
|
|
208
264
|
|
|
209
|
-
signature = sigSSHToASN1(signature,
|
|
265
|
+
signature = sigSSHToASN1(signature, realKeyAlgo);
|
|
210
266
|
if (signature) {
|
|
211
267
|
const sessionID = self._kex.sessionID;
|
|
212
268
|
const blob = Buffer.allocUnsafe(4 + sessionID.length + blobEnd);
|
|
@@ -217,15 +273,16 @@ module.exports = {
|
|
|
217
273
|
4 + sessionID.length
|
|
218
274
|
);
|
|
219
275
|
methodData = {
|
|
220
|
-
keyAlgo,
|
|
276
|
+
keyAlgo: realKeyAlgo,
|
|
221
277
|
key,
|
|
222
278
|
signature,
|
|
223
279
|
blob,
|
|
280
|
+
hashAlgo,
|
|
224
281
|
};
|
|
225
282
|
}
|
|
226
283
|
}
|
|
227
284
|
} else {
|
|
228
|
-
methodData = { keyAlgo, key };
|
|
285
|
+
methodData = { keyAlgo: realKeyAlgo, key, hashAlgo };
|
|
229
286
|
methodDesc = 'publickey -- check';
|
|
230
287
|
}
|
|
231
288
|
}
|
|
@@ -241,10 +298,23 @@ module.exports = {
|
|
|
241
298
|
string signature
|
|
242
299
|
*/
|
|
243
300
|
const keyAlgo = bufferParser.readString(true);
|
|
301
|
+
let realKeyAlgo = keyAlgo;
|
|
244
302
|
const key = bufferParser.readString();
|
|
245
303
|
const localHostname = bufferParser.readString(true);
|
|
246
304
|
const localUsername = bufferParser.readString(true);
|
|
247
305
|
|
|
306
|
+
let hashAlgo;
|
|
307
|
+
switch (keyAlgo) {
|
|
308
|
+
case 'rsa-sha2-256':
|
|
309
|
+
realKeyAlgo = 'ssh-rsa';
|
|
310
|
+
hashAlgo = 'sha256';
|
|
311
|
+
break;
|
|
312
|
+
case 'rsa-sha2-512':
|
|
313
|
+
realKeyAlgo = 'ssh-rsa';
|
|
314
|
+
hashAlgo = 'sha512';
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
|
|
248
318
|
const blobEnd = bufferParser.pos();
|
|
249
319
|
let signature = bufferParser.readString();
|
|
250
320
|
if (signature !== undefined) {
|
|
@@ -254,7 +324,7 @@ module.exports = {
|
|
|
254
324
|
signature = bufferSlice(signature, 4 + keyAlgo.length + 4);
|
|
255
325
|
}
|
|
256
326
|
|
|
257
|
-
signature = sigSSHToASN1(signature,
|
|
327
|
+
signature = sigSSHToASN1(signature, realKeyAlgo);
|
|
258
328
|
if (signature !== undefined) {
|
|
259
329
|
const sessionID = self._kex.sessionID;
|
|
260
330
|
const blob = Buffer.allocUnsafe(4 + sessionID.length + blobEnd);
|
|
@@ -265,12 +335,13 @@ module.exports = {
|
|
|
265
335
|
4 + sessionID.length
|
|
266
336
|
);
|
|
267
337
|
methodData = {
|
|
268
|
-
keyAlgo,
|
|
338
|
+
keyAlgo: realKeyAlgo,
|
|
269
339
|
key,
|
|
270
340
|
signature,
|
|
271
341
|
blob,
|
|
272
342
|
localHostname,
|
|
273
343
|
localUsername,
|
|
344
|
+
hashAlgo
|
|
274
345
|
};
|
|
275
346
|
}
|
|
276
347
|
}
|