Browse Source

ssh: try other OpenSSH key types besides rsa

Eugene Pankov 4 years ago
parent
commit
fdca83ff27

+ 3 - 2
tabby-electron/src/index.ts

@@ -1,7 +1,7 @@
 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 { TerminalColorSchemeProvider } from 'tabby-terminal'
-import { SFTPContextMenuItemProvider, SSHProfileImporter } from 'tabby-ssh'
+import { SFTPContextMenuItemProvider, SSHProfileImporter, AutoPrivateKeyLocator } from 'tabby-ssh'
 import { auditTime } from 'rxjs'
 
 import { HyperColorSchemes } from './colorSchemes'
@@ -17,7 +17,7 @@ import { ElectronService } from './services/electron.service'
 import { ElectronHotkeyProvider } from './hotkeys'
 import { ElectronConfigProvider } from './config'
 import { EditSFTPContextMenu } from './sftpContextMenu'
-import { OpenSSHImporter, StaticFileImporter } from './sshImporters'
+import { OpenSSHImporter, PrivateKeyLocator, StaticFileImporter } from './sshImporters'
 
 @NgModule({
     providers: [
@@ -34,6 +34,7 @@ import { OpenSSHImporter, StaticFileImporter } from './sshImporters'
         { provide: SFTPContextMenuItemProvider, useClass: EditSFTPContextMenu, multi: true },
         { provide: SSHProfileImporter, useExisting: OpenSSHImporter, multi: true },
         { provide: SSHProfileImporter, useExisting: StaticFileImporter, multi: true },
+        { provide: AutoPrivateKeyLocator, useExisting: PrivateKeyLocator, multi: true },
     ],
 })
 export default class ElectronModule {

+ 23 - 1
tabby-electron/src/sshImporters.ts

@@ -5,7 +5,7 @@ import slugify from 'slugify'
 import * as yaml from 'js-yaml'
 import { Injectable } from '@angular/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'
 
@@ -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 - 0
tabby-ssh/src/api/importer.ts

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

+ 1 - 0
tabby-ssh/src/components/sshProfileSettings.component.ts

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

+ 16 - 22
tabby-ssh/src/session/ssh.ts

@@ -1,6 +1,5 @@
 import * as fs from 'mz/fs'
 import * as crypto from 'crypto'
-import * as path from 'path'
 // eslint-disable-next-line @typescript-eslint/no-duplicate-imports, no-duplicate-imports
 import * as sshpk from 'sshpk'
 import colors from 'ansi-colors'
@@ -17,7 +16,7 @@ import { PasswordStorageService } from '../services/passwordStorage.service'
 import { SSHKnownHostsService } from '../services/sshKnownHosts.service'
 import { promisify } from 'util'
 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 { X11Socket } from './x11'
 
@@ -93,6 +92,7 @@ export class SSHSession {
     private config: ConfigService
     private translate: TranslateService
     private knownHosts: SSHKnownHostsService
+    private privateKeyImporters: AutoPrivateKeyLocator[]
 
     constructor (
         private injector: Injector,
@@ -110,6 +110,7 @@ export class SSHSession {
         this.config = injector.get(ConfigService)
         this.translate = injector.get(TranslateService)
         this.knownHosts = injector.get(SSHKnownHostsService)
+        this.privateKeyImporters = injector.get(AutoPrivateKeyLocator, [])
 
         this.willDestroy$.subscribe(() => {
             for (const port of this.forwardedPorts) {
@@ -155,10 +156,15 @@ export class SSHSession {
                     }
                 }
             } else {
-                this.remainingAuthMethods.push({
-                    type: 'publickey',
-                    name: 'auto',
-                })
+                for (const importer of this.privateKeyImporters) {
+                    for (const [name, contents] of await importer.getKeys()) {
+                        this.remainingAuthMethods.push({
+                            type: 'publickey',
+                            name,
+                            contents,
+                        })
+                    }
+                }
             }
         }
         if (!this.profile.options.auth || this.profile.options.auth === 'agent') {
@@ -497,9 +503,9 @@ export class SSHSession {
                     continue
                 }
             }
-            if (method.type === 'publickey') {
+            if (method.type === 'publickey' && method.contents) {
                 try {
-                    const key = await this.loadPrivateKey(method.contents)
+                    const key = await this.loadPrivateKey(method.name!, method.contents)
                     return {
                         type: 'publickey',
                         username: this.authUsername,
@@ -599,20 +605,8 @@ export class SSHSession {
         })
     }
 
-    async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> {
-        if (!privateKeyContents) {
-            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')
+    async loadPrivateKey (name: string, privateKeyContents: Buffer): Promise<string|null> {
+        this.emitServiceMessage(`Loading private key: ${name}`)
         try {
             const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
             this.activePrivateKey = parsedKey.toString('openssh')