mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-20 15:16:03 +00:00
ssh: try other OpenSSH key types besides rsa
This commit is contained in:
@@ -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 {
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -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][]>
|
||||||
|
}
|
||||||
|
@@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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')
|
||||||
|
Reference in New Issue
Block a user