@electerm/ssh2 1.11.2 → 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 +65 -13
- 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 +56 -18
- 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
|
|
|
@@ -627,8 +640,15 @@ class Protocol {
|
|
|
627
640
|
}
|
|
628
641
|
pubKey = pubKey.getPublicSSH();
|
|
629
642
|
|
|
643
|
+
if (typeof keyAlgo === 'function') {
|
|
644
|
+
cbSign = keyAlgo;
|
|
645
|
+
keyAlgo = undefined;
|
|
646
|
+
}
|
|
647
|
+
if (!keyAlgo)
|
|
648
|
+
keyAlgo = keyType;
|
|
649
|
+
|
|
630
650
|
const userLen = Buffer.byteLength(username);
|
|
631
|
-
const algoLen = Buffer.byteLength(
|
|
651
|
+
const algoLen = Buffer.byteLength(keyAlgo);
|
|
632
652
|
const pubKeyLen = pubKey.length;
|
|
633
653
|
const sessionID = this._kex.sessionID;
|
|
634
654
|
const sesLen = sessionID.length;
|
|
@@ -662,7 +682,7 @@ class Protocol {
|
|
|
662
682
|
packet[p += 9] = (cbSign ? 1 : 0);
|
|
663
683
|
|
|
664
684
|
writeUInt32BE(packet, algoLen, ++p);
|
|
665
|
-
packet.utf8Write(
|
|
685
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
666
686
|
|
|
667
687
|
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
668
688
|
packet.set(pubKey, p += 4);
|
|
@@ -705,7 +725,7 @@ class Protocol {
|
|
|
705
725
|
packet[p += 9] = 1;
|
|
706
726
|
|
|
707
727
|
writeUInt32BE(packet, algoLen, ++p);
|
|
708
|
-
packet.utf8Write(
|
|
728
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
709
729
|
|
|
710
730
|
writeUInt32BE(packet, pubKeyLen, p += algoLen);
|
|
711
731
|
packet.set(pubKey, p += 4);
|
|
@@ -713,7 +733,7 @@ class Protocol {
|
|
|
713
733
|
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += pubKeyLen);
|
|
714
734
|
|
|
715
735
|
writeUInt32BE(packet, algoLen, p += 4);
|
|
716
|
-
packet.utf8Write(
|
|
736
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
717
737
|
|
|
718
738
|
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
719
739
|
packet.set(signature, p += 4);
|
|
@@ -728,7 +748,7 @@ class Protocol {
|
|
|
728
748
|
sendPacket(this, this._packetRW.write.finalize(packet));
|
|
729
749
|
});
|
|
730
750
|
}
|
|
731
|
-
authHostbased(username, pubKey, hostname, userlocal, cbSign) {
|
|
751
|
+
authHostbased(username, pubKey, hostname, userlocal, keyAlgo, cbSign) {
|
|
732
752
|
// TODO: Make DRY by sharing similar code with authPK()
|
|
733
753
|
if (this._server)
|
|
734
754
|
throw new Error('Client-only method called in server mode');
|
|
@@ -740,8 +760,15 @@ class Protocol {
|
|
|
740
760
|
const keyType = pubKey.type;
|
|
741
761
|
pubKey = pubKey.getPublicSSH();
|
|
742
762
|
|
|
763
|
+
if (typeof keyAlgo === 'function') {
|
|
764
|
+
cbSign = keyAlgo;
|
|
765
|
+
keyAlgo = undefined;
|
|
766
|
+
}
|
|
767
|
+
if (!keyAlgo)
|
|
768
|
+
keyAlgo = keyType;
|
|
769
|
+
|
|
743
770
|
const userLen = Buffer.byteLength(username);
|
|
744
|
-
const algoLen = Buffer.byteLength(
|
|
771
|
+
const algoLen = Buffer.byteLength(keyAlgo);
|
|
745
772
|
const pubKeyLen = pubKey.length;
|
|
746
773
|
const sessionID = this._kex.sessionID;
|
|
747
774
|
const sesLen = sessionID.length;
|
|
@@ -768,7 +795,7 @@ class Protocol {
|
|
|
768
795
|
data.utf8Write('hostbased', p += 4, 9);
|
|
769
796
|
|
|
770
797
|
writeUInt32BE(data, algoLen, p += 9);
|
|
771
|
-
data.utf8Write(
|
|
798
|
+
data.utf8Write(keyAlgo, p += 4, algoLen);
|
|
772
799
|
|
|
773
800
|
writeUInt32BE(data, pubKeyLen, p += algoLen);
|
|
774
801
|
data.set(pubKey, p += 4);
|
|
@@ -795,7 +822,7 @@ class Protocol {
|
|
|
795
822
|
|
|
796
823
|
writeUInt32BE(packet, 4 + algoLen + 4 + sigLen, p += reqDataLen);
|
|
797
824
|
writeUInt32BE(packet, algoLen, p += 4);
|
|
798
|
-
packet.utf8Write(
|
|
825
|
+
packet.utf8Write(keyAlgo, p += 4, algoLen);
|
|
799
826
|
writeUInt32BE(packet, sigLen, p += algoLen);
|
|
800
827
|
packet.set(signature, p += 4);
|
|
801
828
|
|
|
@@ -2082,4 +2109,29 @@ function modesToBytes(modes) {
|
|
|
2082
2109
|
return bytes;
|
|
2083
2110
|
}
|
|
2084
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
|
+
|
|
2085
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
|
}
|