Skip to content

Commit

Permalink
Apply modified Eugeny/ssh2#rsa-sha as ssh2 patch + add OPENSSH-SHA1 f…
Browse files Browse the repository at this point in the history
…lag to use it (#309)
  • Loading branch information
SchoofsKelvin committed Apr 1, 2023
1 parent 7f138e8 commit 8f62809
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 5 deletions.
96 changes: 96 additions & 0 deletions .yarn/patches/ssh2-npm-1.11.0-convertSha1.patch
Original file line number Diff line number Diff line change
@@ -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) {
19 changes: 18 additions & 1 deletion .yarn/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8799,7 +8799,7 @@ __metadata:
languageName: node
linkType: hard

"ssh2@npm:^1.11.0":
"ssh2@npm:1.11.0":
version: 1.11.0
resolution: "ssh2@npm:1.11.0"
dependencies:
Expand All @@ -8816,6 +8816,23 @@ __metadata:
languageName: node
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":
version: 9.0.1
resolution: "ssri@npm:9.0.1"
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@

# 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 `[email protected]`
- 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)

### Changes
Expand Down
4 changes: 4 additions & 0 deletions common/src/ssh2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ declare module 'ssh2' {
key: string | Buffer | ParsedKey;
/** Optional passphrase in case `key` is an encrypted key */
passphrase?: string;
/** [PATCH:convertSha1#309] If true, make ssh-rsa keys use sha512/sha256 instead of sha1 if possible */
convertSha1?: boolean;
}
export interface AuthHandlerHostBased {
type: 'hostbased';
Expand All @@ -125,6 +127,8 @@ declare module 'ssh2' {
type: 'agent';
username: string;
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 {
type: 'keyboard-interactive';
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,8 @@
"winreg": "^1.2.4"
},
"resolutions": {
"cpu-features": "npm:@favware/[email protected]"
"cpu-features": "npm:@favware/[email protected]",
"ssh2@^1.11.0": "patch:ssh2@npm%3A1.11.0#./.yarn/patches/ssh2-npm-1.11.0-convertSha1.patch"
},
"workspaces": [
"./common",
Expand Down
35 changes: 32 additions & 3 deletions src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import type { FileSystemConfig } from 'common/fileSystemConfig';
import { readFile } from 'fs';
import { Socket } from 'net';
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 * as vscode from 'vscode';
import { getConfig } from './config';
import { getFlagBoolean } from './flags';
import { Logging } from './logging';
import { Logger, Logging } from './logging';
import type { PuttySession } from './putty';
import { toPromise, validatePort } from './utils';

Expand Down Expand Up @@ -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> {
config = (await calculateActualConfig(config))!;
if (!config) return null;
Expand Down Expand Up @@ -253,7 +281,8 @@ export async function createSSH(config: FileSystemConfig, sock?: NodeJS.Readable
reject(error);
});
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]) {
const scope = Logging.scope(`ssh2(${config.name})`);
finalConfig.debug = (msg: string) => scope.debug(msg);
Expand Down
5 changes: 5 additions & 0 deletions src/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { catchingPromise } from './utils';
- Disables the 'diffie-hellman-group-exchange' kex algorithm as a default option
- Originally for issue #239
- 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)
- Enables debug logging in the ssh2 library (set at the start of each connection)
WINDOWS_COMMAND_SEPARATOR (boolean) (default=false)
Expand Down

0 comments on commit 8f62809

Please sign in to comment.