ssh: try other OpenSSH key types besides rsa

This commit is contained in:
Eugene Pankov
2022-02-22 22:03:52 +01:00
parent 1598dab025
commit fdca83ff27
5 changed files with 47 additions and 25 deletions

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core' import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'tabby-core'
import { TerminalColorSchemeProvider } from 'tabby-terminal' import { TerminalColorSchemeProvider } from 'tabby-terminal'
import { SFTPContextMenuItemProvider, SSHProfileImporter } from 'tabby-ssh' import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
import { auditTime } from 'rxjs' import { auditTime } from 'rxjs'
import { HyperColorSchemes } from './colorSchemes' import { HyperColorSchemes } from './colorSchemes'
@@ -17,7 +17,7 @@ import { ElectronService } from './services/electron.service'
import { ElectronHotkeyProvider } from './hotkeys' import { ElectronHotkeyProvider } from './hotkeys'
import { ElectronConfigProvider } from './config' import { ElectronConfigProvider } from './config'
import { EditSFTPContextMenu } from './sftpContextMenu' import { EditSFTPContextMenu } from './sftpContextMenu'
import { OpenSSHImporter, StaticFileImporter } from './sshImporters' import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImporters'
@NgModule({ @NgModule({
providers: [ providers: [
@@ -34,6 +34,7 @@ import { OpenSSHImporter, StaticFileImporter } from './sshImporters'
{ provide: SFTPContextMenuItemProvider, useClass: EditSFTPContextMenu, multi: true }, { provide: SFTPContextMenuItemProvider, useClass: EditSFTPContextMenu, multi: true },
{ provide: SSHProfileImporter, useExisting: OpenSSHImporter, multi: true }, { provide: SSHProfileImporter, useExisting: OpenSSHImporter, multi: true },
{ provide: SSHProfileImporter, useExisting: StaticFileImporter, multi: true }, { provide: SSHProfileImporter, useExisting: StaticFileImporter, multi: true },
{ provide: AutoPrivateKeyLocator, useExisting: PrivateKeyLocator, multi: true },
], ],
}) })
export default class ElectronModule { export default class ElectronModule {

View File

@@ -5,7 +5,7 @@ import slugify from 'slugify'
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { PartialProfile } from 'tabby-core' import { PartialProfile } from 'tabby-core'
import { SSHProfileImporter, PortForwardType, SSHProfile, SSHProfileOptions } from 'tabby-ssh' import { SSHProfileImporter, PortForwardType, SSHProfile, SSHProfileOptions, AutoPrivateKeyLocator } from 'tabby-ssh'
import { ElectronService } from './services/electron.service' import { ElectronService } from './services/electron.service'
@@ -163,3 +163,25 @@ export class StaticFileImporter extends SSHProfileImporter {
})) }))
} }
} }
@Injectable({ providedIn: 'root' })
export class PrivateKeyLocator extends AutoPrivateKeyLocator {
async getKeys (): Promise<[string, Buffer][]> {
const results: [string, Buffer][] = []
const keysPath = path.join(process.env.HOME!, '.ssh')
if (!fsSync.existsSync(keysPath)) {
return results
}
for (const file of await fs.readdir(keysPath)) {
if (/^id_[\w\d]+$/.test(file)) {
const privateKeyContents = await fs.readFile(
path.join(keysPath, file),
{ encoding: null }
)
results.push([file, privateKeyContents])
}
}
return results
}
}

View File

@@ -4,3 +4,7 @@ import { SSHProfile } from './interfaces'
export abstract class SSHProfileImporter { export abstract class SSHProfileImporter {
abstract getProfiles (): Promise<PartialProfile<SSHProfile>[]> abstract getProfiles (): Promise<PartialProfile<SSHProfile>[]>
} }
export abstract class AutoPrivateKeyLocator {
abstract getKeys (): Promise<[string, Buffer][]>
}

View File

@@ -134,6 +134,7 @@ export class SSHProfileSettingsComponent {
proxyCommand: 'Proxy command', proxyCommand: 'Proxy command',
jumpHost: 'Jump host', jumpHost: 'Jump host',
socksProxy: 'SOCKS proxy', socksProxy: 'SOCKS proxy',
httpProxy: 'HTTP proxy',
}[this.connectionMode] }[this.connectionMode]
} }
} }

View File

@@ -1,6 +1,5 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as path from 'path'
// eslint-disable-next-line @typescript-eslint/no-duplicate-imports, no-duplicate-imports // eslint-disable-next-line @typescript-eslint/no-duplicate-imports, no-duplicate-imports
import * as sshpk from 'sshpk' import * as sshpk from 'sshpk'
import colors from 'ansi-colors' import colors from 'ansi-colors'
@@ -17,7 +16,7 @@ import { PasswordStorageService } from '../services/passwordStorage.service'
import { SSHKnownHostsService } from '../services/sshKnownHosts.service' import { SSHKnownHostsService } from '../services/sshKnownHosts.service'
import { promisify } from 'util' import { promisify } from 'util'
import { SFTPSession } from './sftp' import { SFTPSession } from './sftp'
import { ALGORITHM_BLACKLIST, SSHAlgorithmType, PortForwardType, SSHProfile, SSHProxyStream } from '../api' import { ALGORITHM_BLACKLIST, SSHAlgorithmType, PortForwardType, SSHProfile, SSHProxyStream, AutoPrivateKeyLocator } from '../api'
import { ForwardedPort } from './forwards' import { ForwardedPort } from './forwards'
import { X11Socket } from './x11' import { X11Socket } from './x11'
@@ -93,6 +92,7 @@ export class SSHSession {
private config: ConfigService private config: ConfigService
private translate: TranslateService private translate: TranslateService
private knownHosts: SSHKnownHostsService private knownHosts: SSHKnownHostsService
private privateKeyImporters: AutoPrivateKeyLocator[]
constructor ( constructor (
private injector: Injector, private injector: Injector,
@@ -110,6 +110,7 @@ export class SSHSession {
this.config = injector.get(ConfigService) this.config = injector.get(ConfigService)
this.translate = injector.get(TranslateService) this.translate = injector.get(TranslateService)
this.knownHosts = injector.get(SSHKnownHostsService) this.knownHosts = injector.get(SSHKnownHostsService)
this.privateKeyImporters = injector.get(AutoPrivateKeyLocator, [])
this.willDestroy$.subscribe(() => { this.willDestroy$.subscribe(() => {
for (const port of this.forwardedPorts) { for (const port of this.forwardedPorts) {
@@ -155,12 +156,17 @@ export class SSHSession {
} }
} }
} else { } else {
for (const importer of this.privateKeyImporters) {
for (const [name, contents] of await importer.getKeys()) {
this.remainingAuthMethods.push({ this.remainingAuthMethods.push({
type: 'publickey', type: 'publickey',
name: 'auto', name,
contents,
}) })
} }
} }
}
}
if (!this.profile.options.auth || this.profile.options.auth === 'agent') { if (!this.profile.options.auth || this.profile.options.auth === 'agent') {
if (!this.agentPath) { if (!this.agentPath) {
this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running agent is detected`) this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Agent auth selected, but no running agent is detected`)
@@ -497,9 +503,9 @@ export class SSHSession {
continue continue
} }
} }
if (method.type === 'publickey') { if (method.type === 'publickey' && method.contents) {
try { try {
const key = await this.loadPrivateKey(method.contents) const key = await this.loadPrivateKey(method.name!, method.contents)
return { return {
type: 'publickey', type: 'publickey',
username: this.authUsername, username: this.authUsername,
@@ -599,20 +605,8 @@ export class SSHSession {
}) })
} }
async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> { async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<string|null> {
if (!privateKeyContents) { this.emitServiceMessage(`Loading private key: ${name}`)
const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
if (await fs.exists(userKeyPath)) {
this.emitServiceMessage('Using user\'s default private key')
privateKeyContents = await fs.readFile(userKeyPath, { encoding: null })
}
}
if (!privateKeyContents) {
return null
}
this.emitServiceMessage('Loading private key')
try { try {
const parsedKey = await this.parsePrivateKey(privateKeyContents.toString()) const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
this.activePrivateKey = parsedKey.toString('openssh') this.activePrivateKey = parsedKey.toString('openssh')