Apply modified Eugeny/ssh2#rsa-sha as ssh2 patch + add OPENSSH-SHA1 flag to use it (#309)

feature/forwarding
Kelvin Schoofs 2 years ago
parent 7f138e8aa3
commit 8f62809e67

@ -0,0 +1,96 @@
diff --git a/lib/client.js b/lib/client.js
index 80f372a832b71f5bfd18277af7111bdb72930125..9712c5c3f74bb08890dc458efaf8020b988918b0 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -388,8 +388,18 @@ class Client extends EventEmitter {
USERAUTH_PK_OK: (p) => {
if (curAuth.type === 'agent') {
const key = curAuth.agentCtx.currentKey();
+ let algo;
+ if (key.type === 'ssh-rsa' && curAuth.convertSha1) {
+ if (this._protocol._remoteHostKeyAlgorithms.includes('rsa-sha2-512')) {
+ debug && debug('Client: USERAUTH_PK_OK: ssh-rsa key with convertSha1 enabled, switching to sha512');
+ algo = 'sha512';
+ } else if (this._protocol._remoteHostKeyAlgorithms.includes('rsa-sha2-256')) {
+ debug && debug('Client: USERAUTH_PK_OK: ssh-rsa key with convertSha1 enabled, switching to sha256');
+ algo = 'sha256';
+ }
+ }
proto.authPK(curAuth.username, key, (buf, cb) => {
- curAuth.agentCtx.sign(key, buf, {}, (err, signed) => {
+ curAuth.agentCtx.sign(key, buf, { hash: algo }, (err, signed) => {
if (err) {
err.level = 'agent';
this.emit('error', err);
@@ -401,8 +411,18 @@ class Client extends EventEmitter {
});
});
} else if (curAuth.type === 'publickey') {
+ let algo;
+ if (curAuth.key.type === 'ssh-rsa' && curAuth.convertSha1) {
+ if (this._protocol._remoteHostKeyAlgorithms.includes('rsa-sha2-512')) {
+ debug && debug('Client: USERAUTH_PK_OK: ssh-rsa key with convertSha1 enabled, switching to sha512');
+ algo = 'sha512';
+ } else if (this._protocol._remoteHostKeyAlgorithms.includes('rsa-sha2-256')) {
+ debug && debug('Client: USERAUTH_PK_OK: ssh-rsa key with convertSha1 enabled, switching to sha256');
+ algo = 'sha256';
+ }
+ }
proto.authPK(curAuth.username, curAuth.key, (buf, cb) => {
- const signature = curAuth.key.sign(buf);
+ const signature = curAuth.key.sign(buf, algo);
if (signature instanceof Error) {
signature.message =
`Error signing data with key: ${signature.message}`;
@@ -881,7 +901,7 @@ class Client extends EventEmitter {
return skipAuth('Skipping invalid key auth attempt');
if (!key.isPrivateKey())
return skipAuth('Skipping non-private key');
- nextAuth = { type, username, key };
+ nextAuth = { type, username, key, convertSha1: nextAuth.convertSha1 };
break;
}
case 'hostbased': {
@@ -906,7 +926,7 @@ class Client extends EventEmitter {
`Skipping invalid agent: ${nextAuth.agent}`
);
}
- nextAuth = { type, username, agentCtx: new AgentContext(agent) };
+ nextAuth = { type, username, agentCtx: new AgentContext(agent), convertSha1: nextAuth.convertSha1 };
break;
}
case 'keyboard-interactive': {
diff --git a/lib/protocol/Protocol.js b/lib/protocol/Protocol.js
index 94e12bc72b5c61094efd6862dfbce6ff852c5b26..e0cbb748bc80455bfa819cc20c672701c280409c 100644
--- a/lib/protocol/Protocol.js
+++ b/lib/protocol/Protocol.js
@@ -616,7 +616,15 @@ class Protocol {
if (pubKey instanceof Error)
throw new Error('Invalid key');
- const keyType = pubKey.type;
+ let keyType = pubKey.type;
+ if (keyType === 'ssh-rsa') {
+ for (const algo of ['rsa-sha2-512', 'rsa-sha2-256']) {
+ if (this._remoteHostKeyAlgorithms.includes(algo)) {
+ keyType = algo;
+ break;
+ }
+ }
+ }
pubKey = pubKey.getPublicSSH();
const userLen = Buffer.byteLength(username);
diff --git a/lib/protocol/kex.js b/lib/protocol/kex.js
index 49b28f54677809c32b2141c99eec36e0c6d99e38..4ee69bd3b3b5685665d69c05114a94c14cd4b076 100644
--- a/lib/protocol/kex.js
+++ b/lib/protocol/kex.js
@@ -196,6 +196,8 @@ function handleKexInit(self, payload) {
const local = self._offer;
const remote = init;
+
+ self._remoteHostKeyAlgorithms = remote.serverHostKey;
let localKex = local.lists.kex.array;
if (self._compatFlags & COMPAT.BAD_DHGEX) {

@ -8799,7 +8799,7 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ssh2@npm:^1.11.0": "ssh2@npm:1.11.0":
version: 1.11.0 version: 1.11.0
resolution: "ssh2@npm:1.11.0" resolution: "ssh2@npm:1.11.0"
dependencies: dependencies:
@ -8816,6 +8816,23 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"ssh2@patch:ssh2@npm%3A1.11.0#./.yarn/patches/ssh2-npm-1.11.0-convertSha1.patch::locator=vscode-sshfs%40workspace%3A.":
version: 1.11.0
resolution: "ssh2@patch:ssh2@npm%3A1.11.0#./.yarn/patches/ssh2-npm-1.11.0-convertSha1.patch::version=1.11.0&hash=602c9b&locator=vscode-sshfs%40workspace%3A."
dependencies:
asn1: ^0.2.4
bcrypt-pbkdf: ^1.0.2
cpu-features: ~0.0.4
nan: ^2.16.0
dependenciesMeta:
cpu-features:
optional: true
nan:
optional: true
checksum: f33a0074637f195b261952cea4b8b02c15977375f59d985580c0c571ac6bac56cf523cd85ee99d1fdcdcce4e6547271c35b2450c0c14963f11ff88b0093d8b8e
languageName: node
linkType: hard
"ssri@npm:^9.0.0": "ssri@npm:^9.0.0":
version: 9.0.1 version: 9.0.1
resolution: "ssri@npm:9.0.1" resolution: "ssri@npm:9.0.1"

@ -1,6 +1,17 @@
# Changelog # Changelog
## Unreleased
### Changes
- Apply a patch to ssh2 and make use of it to fix OpenSSH 8.8+ disabling `ssh-rsa` (SHA1) by default (#309)
- Patch file in `.yarn/patches` based on <https://github.com/Eugeny/ssh2/tree/rsa-sha> applied to `ssh2@1.11.0`
- The patch adds an option `convertSha1` to `publickey` and `agent` authentication methods on top of Eugeny's modifications
- When the option is present, `ssh-rsa` keys will be treated as `rsa-sha2-512` or `rsa-sha2-256`, if the server supports it
- Added a flag `OPENSSH-SHA1` (enabled by default) to pass this `convertSha1` flag when using `publickey` or `agent` auths
- Part of this change required creating a custom ssh2 `authHandler` (based on the built-in version) to pass the option if desired
## v1.26.0 (2023-03-25) ## v1.26.0 (2023-03-25)
### Changes ### Changes

@ -110,6 +110,8 @@ declare module 'ssh2' {
key: string | Buffer | ParsedKey; key: string | Buffer | ParsedKey;
/** Optional passphrase in case `key` is an encrypted key */ /** Optional passphrase in case `key` is an encrypted key */
passphrase?: string; passphrase?: string;
/** [PATCH:convertSha1#309] If true, make ssh-rsa keys use sha512/sha256 instead of sha1 if possible */
convertSha1?: boolean;
} }
export interface AuthHandlerHostBased { export interface AuthHandlerHostBased {
type: 'hostbased'; type: 'hostbased';
@ -125,6 +127,8 @@ declare module 'ssh2' {
type: 'agent'; type: 'agent';
username: string; username: string;
agent: string | BaseAgent; agent: string | BaseAgent;
/** [PATCH:convertSha1#309] If true, make ssh-rsa keys use sha512/sha256 instead of sha1 if possible */
convertSha1?: boolean;
} }
export interface AuthHandlerKeyboardInteractive { export interface AuthHandlerKeyboardInteractive {
type: 'keyboard-interactive'; type: 'keyboard-interactive';

@ -436,7 +436,8 @@
"winreg": "^1.2.4" "winreg": "^1.2.4"
}, },
"resolutions": { "resolutions": {
"cpu-features": "npm:@favware/skip-dependency@1.1.3" "cpu-features": "npm:@favware/skip-dependency@1.1.3",
"ssh2@^1.11.0": "patch:ssh2@npm%3A1.11.0#./.yarn/patches/ssh2-npm-1.11.0-convertSha1.patch"
}, },
"workspaces": [ "workspaces": [
"./common", "./common",

@ -2,12 +2,12 @@ import type { FileSystemConfig } from 'common/fileSystemConfig';
import { readFile } from 'fs'; import { readFile } from 'fs';
import { Socket } from 'net'; import { Socket } from 'net';
import { userInfo } from 'os'; import { userInfo } from 'os';
import { Client, ClientChannel, ConnectConfig } from 'ssh2'; import { AuthHandlerFunction, AuthHandlerObject, Client, ClientChannel, ConnectConfig } from 'ssh2';
import { SFTP } from 'ssh2/lib/protocol/SFTP'; import { SFTP } from 'ssh2/lib/protocol/SFTP';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { getConfig } from './config'; import { getConfig } from './config';
import { getFlagBoolean } from './flags'; import { getFlagBoolean } from './flags';
import { Logging } from './logging'; import { Logger, Logging } from './logging';
import type { PuttySession } from './putty'; import type { PuttySession } from './putty';
import { toPromise, validatePort } from './utils'; import { toPromise, validatePort } from './utils';
@ -225,6 +225,34 @@ export async function createSocket(config: FileSystemConfig): Promise<NodeJS.Rea
}); });
} }
function makeAuthHandler(config: FileSystemConfig, logging: Logger): AuthHandlerFunction {
const authsAllowed: (AuthHandlerObject | AuthHandlerObject['type'])[] = ['none'];
const [flagV, flagR] = getFlagBoolean('OPENSSH-SHA1', true, config.flags);
if (config.password) authsAllowed.push('password');
if (config.privateKey) {
if (flagV) {
logging.info`Flag "OPENSSH-SHA1" enabled due to '${flagR}', including convertSha1 for publickey authentication`;
authsAllowed.push({ type: 'publickey', username: config.username!, key: config.privateKey, convertSha1: true });
} else {
authsAllowed.push('publickey');
}
}
if (config.agent) {
if (flagV) {
logging.info`Flag "OPENSSH-SHA1" enabled due to '${flagR}', including convertSha1 for agent authentication`;
authsAllowed.push({ type: 'agent', username: config.username!, agent: config.agent, convertSha1: true });
} else {
authsAllowed.push('agent');
}
}
if (config.tryKeyboard) authsAllowed.push('keyboard-interactive');
if (config.privateKey && config.localHostname && config.localUsername) authsAllowed.push('hostbased');
if (flagV) {
logging.info`Flag "OPENSSH-SHA1" enabled due to '${flagR}'`;
}
return () => authsAllowed.shift() || false;
}
export async function createSSH(config: FileSystemConfig, sock?: NodeJS.ReadableStream): Promise<Client | null> { export async function createSSH(config: FileSystemConfig, sock?: NodeJS.ReadableStream): Promise<Client | null> {
config = (await calculateActualConfig(config))!; config = (await calculateActualConfig(config))!;
if (!config) return null; if (!config) return null;
@ -253,7 +281,8 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
reject(error); reject(error);
}); });
try { try {
const finalConfig: ConnectConfig = { ...config, sock, ...DEFAULT_CONFIG }; const finalConfig: FileSystemConfig = { ...config, sock, ...DEFAULT_CONFIG };
finalConfig.authHandler = makeAuthHandler(finalConfig, logging);
if (config.debug || getFlagBoolean('DEBUG_SSH2', false, config.flags)[0]) { if (config.debug || getFlagBoolean('DEBUG_SSH2', false, config.flags)[0]) {
const scope = Logging.scope(`ssh2(${config.name})`); const scope = Logging.scope(`ssh2(${config.name})`);
finalConfig.debug = (msg: string) => scope.debug(msg); finalConfig.debug = (msg: string) => scope.debug(msg);

@ -8,6 +8,11 @@ import { catchingPromise } from './utils';
- Disables the 'diffie-hellman-group-exchange' kex algorithm as a default option - Disables the 'diffie-hellman-group-exchange' kex algorithm as a default option
- Originally for issue #239 - Originally for issue #239
- Automatically enabled for Electron v11.0, v11.1 and v11.2 - Automatically enabled for Electron v11.0, v11.1 and v11.2
OPENSSH-SHA1 (boolean) (default=true)
- Patch for issue #309 where OpenSSH 8.8+ refuses `ssh-rsa` keys using SHA1 (which is what ssh2 uses)
- The patch (see `.yarn/patches/*-convertSha1.patch`) adds an option for `agent` and `publickey` authentications
- With this option enabled, the patch will, if the server supports it, make ssh2 use SHA512/SHA256 for `ssh-rsa` keys
/ Mind that this option applies for every server, the patch doesn't (currently) check whether it's OpenSSH 8.8+
DEBUG_SSH2 (boolean) (default=false) DEBUG_SSH2 (boolean) (default=false)
- Enables debug logging in the ssh2 library (set at the start of each connection) - Enables debug logging in the ssh2 library (set at the start of each connection)
WINDOWS_COMMAND_SEPARATOR (boolean) (default=false) WINDOWS_COMMAND_SEPARATOR (boolean) (default=false)

Loading…
Cancel
Save