Browse Source

allow storing private keys in the vault

Eugene Pankov 4 years ago
parent
commit
51f62c9719

+ 1 - 1
package.json

@@ -74,7 +74,7 @@
     "docs": "typedoc --out docs/api --tsconfig terminus-core/src/tsconfig.typings.json terminus-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src/index.ts && typedoc --out docs/api/local --tsconfig terminus-local/tsconfig.typings.json terminus-local/src/index.ts && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src/index.ts",
     "docs": "typedoc --out docs/api --tsconfig terminus-core/src/tsconfig.typings.json terminus-core/src/index.ts && typedoc --out docs/api/terminal --tsconfig terminus-terminal/tsconfig.typings.json terminus-terminal/src/index.ts && typedoc --out docs/api/local --tsconfig terminus-local/tsconfig.typings.json terminus-local/src/index.ts && typedoc --out docs/api/settings --tsconfig terminus-settings/tsconfig.typings.json terminus-settings/src/index.ts",
     "lint": "eslint --ext ts */src */lib",
     "lint": "eslint --ext ts */src */lib",
     "postinstall": "node ./scripts/install-deps.js",
     "postinstall": "node ./scripts/install-deps.js",
-    "patch": "patch-package"
+    "patch": "patch-package; cd web; patch-package"
   },
   },
   "private": true
   "private": true
 }
 }

+ 13 - 0
terminus-core/src/api/fileProvider.ts

@@ -0,0 +1,13 @@
+import { Injectable } from '@angular/core'
+
+@Injectable({ providedIn: 'root' })
+export abstract class FileProvider {
+    name: string
+
+    async isAvailable (): Promise<boolean> {
+        return true
+    }
+
+    abstract selectAndStoreFile (description: string): Promise<string>
+    abstract retrieveFile (key: string): Promise<Buffer>
+}

+ 3 - 1
terminus-core/src/api/index.ts

@@ -15,6 +15,7 @@ export { MenuItemOptions } from './menu'
 export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
 export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
 export { HostWindowService } from './hostWindow'
 export { HostWindowService } from './hostWindow'
 export { HostAppService, Platform } from './hostApp'
 export { HostAppService, Platform } from './hostApp'
+export { FileProvider } from './fileProvider'
 
 
 export { AppService } from '../services/app.service'
 export { AppService } from '../services/app.service'
 export { ConfigService } from '../services/config.service'
 export { ConfigService } from '../services/config.service'
@@ -27,5 +28,6 @@ export { NotificationsService } from '../services/notifications.service'
 export { ThemesService } from '../services/themes.service'
 export { ThemesService } from '../services/themes.service'
 export { TabsService } from '../services/tabs.service'
 export { TabsService } from '../services/tabs.service'
 export { UpdaterService } from '../services/updater.service'
 export { UpdaterService } from '../services/updater.service'
-export { VaultService, Vault, VaultSecret } from '../services/vault.service'
+export { VaultService, Vault, VaultSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service'
+export { FileProvidersService } from '../services/fileProviders.service'
 export * from '../utils'
 export * from '../utils'

+ 3 - 1
terminus-core/src/index.ts

@@ -27,10 +27,11 @@ import { AutofocusDirective } from './directives/autofocus.directive'
 import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
 import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
 import { DropZoneDirective } from './directives/dropZone.directive'
 import { DropZoneDirective } from './directives/dropZone.directive'
 
 
-import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService } from './api'
+import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider } from './api'
 
 
 import { AppService } from './services/app.service'
 import { AppService } from './services/app.service'
 import { ConfigService } from './services/config.service'
 import { ConfigService } from './services/config.service'
+import { VaultFileProvider } from './services/vault.service'
 
 
 import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
 import { StandardTheme, StandardCompactTheme, PaperTheme } from './theme'
 import { CoreConfigProvider } from './config'
 import { CoreConfigProvider } from './config'
@@ -53,6 +54,7 @@ const PROVIDERS = [
     { provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
     { provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
     { provide: CLIHandler, useClass: LastCLIHandler, multi: true },
     { provide: CLIHandler, useClass: LastCLIHandler, multi: true },
     { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
     { provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
+    { provide: FileProvider, useClass: VaultFileProvider, multi: true },
 ]
 ]
 
 
 /** @hidden */
 /** @hidden */

+ 48 - 0
terminus-core/src/services/fileProviders.service.ts

@@ -0,0 +1,48 @@
+import { Inject, Injectable } from '@angular/core'
+import { AppService, FileProvider, NotificationsService } from '../api'
+
+@Injectable({ providedIn: 'root' })
+export class FileProvidersService {
+    /** @hidden */
+    private constructor (
+        private app: AppService,
+        private notifications: NotificationsService,
+        @Inject(FileProvider) private fileProviders: FileProvider[],
+    ) { }
+
+    async selectAndStoreFile (description: string): Promise<string> {
+        const p = await this.selectProvider()
+        return p.selectAndStoreFile(description)
+    }
+
+    async retrieveFile (key: string): Promise<Buffer> {
+        for (const p of this.fileProviders) {
+            try {
+                return await p.retrieveFile(key)
+            } catch {
+                continue
+            }
+        }
+        throw new Error('Not found')
+    }
+
+    async selectProvider (): Promise<FileProvider> {
+        const providers: FileProvider[] = []
+        await Promise.all(this.fileProviders.map(async p => {
+            if (await p.isAvailable()) {
+                providers.push(p)
+            }
+        }))
+        if (!providers.length) {
+            this.notifications.error('Vault master passphrase needs to be set to allow storing secrets')
+            throw new Error('No available file providers')
+        }
+        if (providers.length === 1) {
+            return providers[0]
+        }
+        return this.app.showSelector('Select file storage', providers.map(p => ({
+            name: p.name,
+            result: p,
+        })))
+    }
+}

+ 53 - 1
terminus-core/src/services/vault.service.ts

@@ -6,6 +6,8 @@ import { AsyncSubject, Subject, Observable } from 'rxjs'
 import { wrapPromise } from '../utils'
 import { wrapPromise } from '../utils'
 import { UnlockVaultModalComponent } from '../components/unlockVaultModal.component'
 import { UnlockVaultModalComponent } from '../components/unlockVaultModal.component'
 import { NotificationsService } from '../services/notifications.service'
 import { NotificationsService } from '../services/notifications.service'
+import { FileProvider } from '../api/fileProvider'
+import { PlatformService } from '../api/platform'
 
 
 const PBKDF_ITERATIONS = 100000
 const PBKDF_ITERATIONS = 100000
 const PBKDF_DIGEST = 'sha512'
 const PBKDF_DIGEST = 'sha512'
@@ -80,6 +82,8 @@ async function decryptVault (vault: StoredVault, passphrase: string): Promise<Va
     return migrateVaultContent(JSON.parse(plaintext))
     return migrateVaultContent(JSON.parse(plaintext))
 }
 }
 
 
+export const VAULT_SECRET_TYPE_FILE = 'file'
+
 // Don't make it accessible through VaultService fields
 // Don't make it accessible through VaultService fields
 let _rememberedPassphrase: string|null = null
 let _rememberedPassphrase: string|null = null
 
 
@@ -161,7 +165,7 @@ export class VaultService {
             setTimeout(() => {
             setTimeout(() => {
                 _rememberedPassphrase = null
                 _rememberedPassphrase = null
                 // avoid multiple consequent prompts
                 // avoid multiple consequent prompts
-            }, Math.min(1000, rememberFor * 60000))
+            }, Math.max(1000, rememberFor * 60000))
             _rememberedPassphrase = passphrase
             _rememberedPassphrase = passphrase
         }
         }
 
 
@@ -212,3 +216,51 @@ export class VaultService {
         return !!this.store
         return !!this.store
     }
     }
 }
 }
+
+
+@Injectable()
+export class VaultFileProvider extends FileProvider {
+    name = 'Vault'
+    prefix = 'vault://'
+
+    constructor (
+        private vault: VaultService,
+        private platform: PlatformService,
+        private zone: NgZone,
+    ) {
+        super()
+    }
+
+    async isAvailable (): Promise<boolean> {
+        return this.vault.isEnabled()
+    }
+
+    async selectAndStoreFile (description: string): Promise<string> {
+        const transfers = await this.platform.startUpload()
+        if (!transfers.length) {
+            throw new Error('Nothing selected')
+        }
+        const transfer = transfers[0]
+        const id = (await wrapPromise(this.zone, promisify(crypto.randomBytes)(32))).toString('hex')
+        this.vault.addSecret({
+            type: VAULT_SECRET_TYPE_FILE,
+            key: {
+                id,
+                description,
+            },
+            value: (await transfer.readAll()).toString('base64'),
+        })
+        return `${this.prefix}${id}`
+    }
+
+    async retrieveFile (key: string): Promise<Buffer> {
+        if (!key.startsWith(this.prefix)) {
+            throw new Error('Incorrect type')
+        }
+        const secret = await this.vault.getSecret(VAULT_SECRET_TYPE_FILE, { id: key.substring(this.prefix.length) })
+        if (!secret) {
+            throw new Error('Not found')
+        }
+        return Buffer.from(secret.value, 'base64')
+    }
+}

+ 3 - 1
terminus-electron/src/index.ts

@@ -1,5 +1,5 @@
 import { NgModule } from '@angular/core'
 import { NgModule } from '@angular/core'
-import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, ElectronService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider } from 'terminus-core'
+import { PlatformService, LogService, UpdaterService, DockingService, HostAppService, ThemesService, Platform, AppService, ConfigService, ElectronService, WIN_BUILD_FLUENT_BG_SUPPORTED, isWindowsBuild, HostWindowService, HotkeyProvider, ConfigProvider, FileProvider } from 'terminus-core'
 import { TerminalColorSchemeProvider } from 'terminus-terminal'
 import { TerminalColorSchemeProvider } from 'terminus-terminal'
 
 
 import { HyperColorSchemes } from './colorSchemes'
 import { HyperColorSchemes } from './colorSchemes'
@@ -9,6 +9,7 @@ import { ElectronUpdaterService } from './services/updater.service'
 import { TouchbarService } from './services/touchbar.service'
 import { TouchbarService } from './services/touchbar.service'
 import { ElectronDockingService } from './services/docking.service'
 import { ElectronDockingService } from './services/docking.service'
 import { ElectronHostWindow } from './services/hostWindow.service'
 import { ElectronHostWindow } from './services/hostWindow.service'
+import { ElectronFileProvider } from './services/fileProvider.service'
 import { ElectronHostAppService } from './services/hostApp.service'
 import { ElectronHostAppService } from './services/hostApp.service'
 import { ElectronHotkeyProvider } from './hotkeys'
 import { ElectronHotkeyProvider } from './hotkeys'
 import { ElectronConfigProvider } from './config'
 import { ElectronConfigProvider } from './config'
@@ -24,6 +25,7 @@ import { ElectronConfigProvider } from './config'
         { provide: DockingService, useClass: ElectronDockingService },
         { provide: DockingService, useClass: ElectronDockingService },
         { provide: HotkeyProvider, useClass: ElectronHotkeyProvider, multi: true },
         { provide: HotkeyProvider, useClass: ElectronHotkeyProvider, multi: true },
         { provide: ConfigProvider, useClass: ElectronConfigProvider, multi: true },
         { provide: ConfigProvider, useClass: ElectronConfigProvider, multi: true },
+        { provide: FileProvider, useClass: ElectronFileProvider, multi: true },
     ],
     ],
 })
 })
 export default class ElectronModule {
 export default class ElectronModule {

+ 40 - 0
terminus-electron/src/services/fileProvider.service.ts

@@ -0,0 +1,40 @@
+import { promises as fs } from 'fs'
+import { Injectable } from '@angular/core'
+import { ElectronService, FileProvider } from 'terminus-core'
+import { ElectronHostWindow } from './hostWindow.service'
+
+@Injectable()
+export class ElectronFileProvider extends FileProvider {
+    name = 'Filesystem'
+
+    constructor (
+        private electron: ElectronService,
+        private hostWindow: ElectronHostWindow,
+    ) {
+        super()
+    }
+
+    async selectAndStoreFile (description: string): Promise<string> {
+        const result = await this.electron.dialog.showOpenDialog(
+            this.hostWindow.getWindow(),
+            {
+                buttonLabel: `Select ${description}`,
+                properties: ['openFile', 'treatPackageAsDirectory'],
+            },
+        )
+        if (result.canceled || !result.filePaths.length) {
+            throw new Error('canceled')
+        }
+
+        return `file://${result.filePaths[0]}`
+    }
+
+    async retrieveFile (key: string): Promise<Buffer> {
+        if (key.startsWith('file://')) {
+            key = key.substring('file://'.length)
+        } else if (key.includes('://')) {
+            throw new Error('Incorrect type')
+        }
+        return fs.readFile(key, { encoding: null })
+    }
+}

+ 4 - 1
terminus-settings/src/components/vaultSettingsTab.component.ts

@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
 import { Component } from '@angular/core'
 import { Component } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService } from 'terminus-core'
+import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE } from 'terminus-core'
 import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
 import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
 
 
 
 
@@ -78,6 +78,9 @@ export class VaultSettingsTabComponent extends BaseComponent {
         if (secret.type === 'ssh:key-passphrase') {
         if (secret.type === 'ssh:key-passphrase') {
             return `Passphrase for a private key with hash ${secret.key.hash.substring(0, 8)}...`
             return `Passphrase for a private key with hash ${secret.key.hash.substring(0, 8)}...`
         }
         }
+        if (secret.type === VAULT_SECRET_TYPE_FILE) {
+            return `File: ${secret.key.description}`
+        }
         return `Unknown secret of type ${secret.type} for ${JSON.stringify(secret.key)}`
         return `Unknown secret of type ${secret.type} for ${JSON.stringify(secret.key)}`
     }
     }
 
 

+ 19 - 14
terminus-ssh/src/api.ts

@@ -10,7 +10,7 @@ import stripAnsi from 'strip-ansi'
 import socksv5 from 'socksv5'
 import socksv5 from 'socksv5'
 import { Injector, NgZone } from '@angular/core'
 import { Injector, NgZone } from '@angular/core'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
 import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
-import { HostAppService, Logger, NotificationsService, Platform, PlatformService, wrapPromise } from 'terminus-core'
+import { FileProvidersService, HostAppService, Logger, NotificationsService, Platform, PlatformService, wrapPromise } from 'terminus-core'
 import { BaseSession } from 'terminus-terminal'
 import { BaseSession } from 'terminus-terminal'
 import { Server, Socket, createServer, createConnection } from 'net'
 import { Server, Socket, createServer, createConnection } from 'net'
 import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
 import { Client, ClientChannel, SFTPWrapper } from 'ssh2'
@@ -138,7 +138,8 @@ export class ForwardedPort implements ForwardedPortConfig {
 
 
 interface AuthMethod {
 interface AuthMethod {
     type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
     type: 'none'|'publickey'|'agent'|'password'|'keyboard-interactive'|'hostbased'
-    path?: string
+    name?: string
+    contents?: Buffer
 }
 }
 
 
 export interface SFTPFile {
 export interface SFTPFile {
@@ -279,10 +280,11 @@ export class SSHSession extends BaseSession {
     private platform: PlatformService
     private platform: PlatformService
     private notifications: NotificationsService
     private notifications: NotificationsService
     private zone: NgZone
     private zone: NgZone
+    private fileProviders: FileProvidersService
 
 
     constructor (
     constructor (
         injector: Injector,
         injector: Injector,
-        public connection: SSHConnection
+        public connection: SSHConnection,
     ) {
     ) {
         super()
         super()
         this.passwordStorage = injector.get(PasswordStorageService)
         this.passwordStorage = injector.get(PasswordStorageService)
@@ -291,6 +293,7 @@ export class SSHSession extends BaseSession {
         this.platform = injector.get(PlatformService)
         this.platform = injector.get(PlatformService)
         this.notifications = injector.get(NotificationsService)
         this.notifications = injector.get(NotificationsService)
         this.zone = injector.get(NgZone)
         this.zone = injector.get(NgZone)
+        this.fileProviders = injector.get(FileProvidersService)
 
 
         this.scripts = connection.scripts ?? []
         this.scripts = connection.scripts ?? []
         this.destroyed$.subscribe(() => {
         this.destroyed$.subscribe(() => {
@@ -318,11 +321,14 @@ export class SSHSession extends BaseSession {
         this.remainingAuthMethods = [{ type: 'none' }]
         this.remainingAuthMethods = [{ type: 'none' }]
         if (!this.connection.auth || this.connection.auth === 'publicKey') {
         if (!this.connection.auth || this.connection.auth === 'publicKey') {
             for (const pk of this.connection.privateKeys ?? []) {
             for (const pk of this.connection.privateKeys ?? []) {
-                if (await fs.exists(pk)) {
+                try {
                     this.remainingAuthMethods.push({
                     this.remainingAuthMethods.push({
                         type: 'publickey',
                         type: 'publickey',
-                        path: pk,
+                        name: pk,
+                        contents: await this.fileProviders.retrieveFile(pk),
                     })
                     })
+                } catch (error) {
+                    this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Could not load private key ${pk}: ${error}`)
                 }
                 }
             }
             }
         }
         }
@@ -561,14 +567,14 @@ export class SSHSession extends BaseSession {
             }
             }
             if (method.type === 'publickey') {
             if (method.type === 'publickey') {
                 try {
                 try {
-                    const key = await this.loadPrivateKey(method.path!)
+                    const key = await this.loadPrivateKey(method.contents)
                     return {
                     return {
                         type: 'publickey',
                         type: 'publickey',
                         username: this.connection.user,
                         username: this.connection.user,
                         key,
                         key,
                     }
                     }
                 } catch (e) {
                 } catch (e) {
-                    this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.path}: ${e}`)
+                    this.emitServiceMessage(colors.bgYellow.yellow.black(' ! ') + ` Failed to load private key ${method.name}: ${e}`)
                     continue
                     continue
                 }
                 }
             }
             }
@@ -706,23 +712,22 @@ export class SSHSession extends BaseSession {
         }
         }
     }
     }
 
 
-    async loadPrivateKey (privateKeyPath: string): Promise<string|null> {
-        if (!privateKeyPath) {
+    async loadPrivateKey (privateKeyContents?: Buffer): Promise<string|null> {
+        if (!privateKeyContents) {
             const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
             const userKeyPath = path.join(process.env.HOME!, '.ssh', 'id_rsa')
             if (await fs.exists(userKeyPath)) {
             if (await fs.exists(userKeyPath)) {
                 this.emitServiceMessage('Using user\'s default private key')
                 this.emitServiceMessage('Using user\'s default private key')
-                privateKeyPath = userKeyPath
+                privateKeyContents = fs.readFile(userKeyPath, { encoding: null })
             }
             }
         }
         }
 
 
-        if (!privateKeyPath) {
+        if (!privateKeyContents) {
             return null
             return null
         }
         }
 
 
-        this.emitServiceMessage('Loading private key from ' + colors.bgWhite.blackBright(' ' + privateKeyPath + ' '))
+        this.emitServiceMessage('Loading private key')
         try {
         try {
-            const privateKeyContents = (await fs.readFile(privateKeyPath)).toString()
-            const parsedKey = await this.parsePrivateKey(privateKeyContents)
+            const parsedKey = await this.parsePrivateKey(privateKeyContents.toString())
             this.activePrivateKey = parsedKey.toString('openssh')
             this.activePrivateKey = parsedKey.toString('openssh')
             return this.activePrivateKey
             return this.activePrivateKey
         } catch (error) {
         } catch (error) {

+ 1 - 1
terminus-ssh/src/components/editConnectionModal.component.pug

@@ -91,7 +91,7 @@
                     .list-group.mb-2
                     .list-group.mb-2
                         .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let path of connection.privateKeys')
                         .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let path of connection.privateKeys')
                             i.fas.fa-key
                             i.fas.fa-key
-                            .mr-auto {{path}}
+                            .no-wrap.mr-auto {{path}}
                             button.btn.btn-link((click)='removePrivateKey(path)')
                             button.btn.btn-link((click)='removePrivateKey(path)')
                                 i.fas.fa-trash
                                 i.fas.fa-trash
                     button.btn.btn-secondary((click)='addPrivateKey()')
                     button.btn.btn-secondary((click)='addPrivateKey()')

+ 8 - 16
terminus-ssh/src/components/editConnectionModal.component.ts

@@ -4,7 +4,7 @@ import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 import { Observable } from 'rxjs'
 import { Observable } from 'rxjs'
 import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
 import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'
 
 
-import { ElectronService, ConfigService, PlatformService } from 'terminus-core'
+import { ConfigService, PlatformService, FileProvidersService } from 'terminus-core'
 import { PasswordStorageService } from '../services/passwordStorage.service'
 import { PasswordStorageService } from '../services/passwordStorage.service'
 import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
 import { SSHConnection, LoginScript, ForwardedPortConfig, SSHAlgorithmType, ALGORITHM_BLACKLIST } from '../api'
 import { PromptModalComponent } from './promptModal.component'
 import { PromptModalComponent } from './promptModal.component'
@@ -28,10 +28,10 @@ export class EditConnectionModalComponent {
     constructor (
     constructor (
         public config: ConfigService,
         public config: ConfigService,
         private modalInstance: NgbActiveModal,
         private modalInstance: NgbActiveModal,
-        private electron: ElectronService,
         private platform: PlatformService,
         private platform: PlatformService,
         private passwordStorage: PasswordStorageService,
         private passwordStorage: PasswordStorageService,
         private ngbModal: NgbModal,
         private ngbModal: NgbModal,
+        private fileProviders: FileProvidersService,
     ) {
     ) {
         for (const k of Object.values(SSHAlgorithmType)) {
         for (const k of Object.values(SSHAlgorithmType)) {
             const supportedAlg = {
             const supportedAlg = {
@@ -101,20 +101,12 @@ export class EditConnectionModalComponent {
         this.passwordStorage.deletePassword(this.connection)
         this.passwordStorage.deletePassword(this.connection)
     }
     }
 
 
-    addPrivateKey () {
-        this.electron.dialog.showOpenDialog(
-            {
-                defaultPath: this.connection.privateKeys![0],
-                title: 'Select private key',
-            }
-        ).then(result => {
-            if (!result.canceled) {
-                this.connection.privateKeys = [
-                    ...this.connection.privateKeys!,
-                    ...result.filePaths,
-                ]
-            }
-        })
+    async addPrivateKey () {
+        const ref = await this.fileProviders.selectAndStoreFile(`private key for ${this.connection.name}`)
+        this.connection.privateKeys = [
+            ...this.connection.privateKeys!,
+            ref,
+        ]
     }
     }
 
 
     removePrivateKey (path: string) {
     removePrivateKey (path: string) {

+ 1 - 0
web/package.json

@@ -7,6 +7,7 @@
         "crypto-browserify": "^3.12.0",
         "crypto-browserify": "^3.12.0",
         "deepmerge": "^4.2.2",
         "deepmerge": "^4.2.2",
         "events": "^3.3.0",
         "events": "^3.3.0",
+        "patch-package": "^6.4.7",
         "path-browserify": "^1.0.1",
         "path-browserify": "^1.0.1",
         "stream-browserify": "^3.0.0"
         "stream-browserify": "^3.0.0"
     },
     },

+ 13 - 0
web/patches/browserify-sign+4.2.1.patch

@@ -0,0 +1,13 @@
+diff --git a/node_modules/browserify-sign/browser/index.js b/node_modules/browserify-sign/browser/index.js
+index e6df44c..641e18e 100644
+--- a/node_modules/browserify-sign/browser/index.js
++++ b/node_modules/browserify-sign/browser/index.js
+@@ -11,6 +11,8 @@ Object.keys(algorithms).forEach(function (key) {
+   algorithms[key.toLowerCase()] = algorithms[key]
+ })
+ 
++algorithms['sha1'] = algorithms['RSA-SHA1']
++
+ function Sign (algorithm) {
+   stream.Writable.call(this)
+ 

+ 28 - 0
web/polyfills.buffer.ts

@@ -206,3 +206,31 @@ function utf8ToBytes (string, units) {
     }
     }
     return bytes
     return bytes
 }
 }
+
+// Create lookup table for `toString('hex')`
+// See: https://github.com/feross/buffer/issues/219
+const hexSliceLookupTable = (function () {
+    const alphabet = '0123456789abcdef'
+    const table = new Array(256)
+    for (let i = 0; i < 16; ++i) {
+        const i16 = i * 16
+        for (let j = 0; j < 16; ++j) {
+            table[i16 + j] = alphabet[i] + alphabet[j]
+        }
+    }
+    return table
+})()
+
+
+export function hexSlice (start, end) {
+    const len = this.length
+
+    if (!start || start < 0) {start = 0}
+    if (!end || end < 0 || end > len) {end = len}
+
+    let out = ''
+    for (let i = start; i < end; ++i) {
+        out += hexSliceLookupTable[this[i]]
+    }
+    return out
+}

+ 2 - 1
web/polyfills.ts

@@ -10,7 +10,7 @@ import * as ngBootstrapModule from '@ng-bootstrap/ng-bootstrap'
 import * as ngxToastrModule from 'ngx-toastr'
 import * as ngxToastrModule from 'ngx-toastr'
 
 
 import { Buffer } from 'buffer'
 import { Buffer } from 'buffer'
-import { base64Slice, latin1Slice, utf8Slice, utf8Write } from './polyfills.buffer'
+import { base64Slice, hexSlice, latin1Slice, utf8Slice, utf8Write } from './polyfills.buffer'
 
 
 
 
 window['Buffer'] = Buffer
 window['Buffer'] = Buffer
@@ -19,6 +19,7 @@ Buffer.prototype['latin1Slice'] = latin1Slice
 Buffer.prototype['utf8Slice'] = utf8Slice
 Buffer.prototype['utf8Slice'] = utf8Slice
 Buffer.prototype['base64Slice'] = base64Slice
 Buffer.prototype['base64Slice'] = base64Slice
 Buffer.prototype['utf8Write'] = utf8Write
 Buffer.prototype['utf8Write'] = utf8Write
+Buffer.prototype['hexSlice'] = hexSlice
 
 
 const mocks = {
 const mocks = {
     fs: {
     fs: {

+ 327 - 6
web/yarn.lock

@@ -2,6 +2,18 @@
 # yarn lockfile v1
 # yarn lockfile v1
 
 
 
 
+"@yarnpkg/lockfile@^1.1.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+  integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
+ansi-styles@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+  integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+  dependencies:
+    color-convert "^1.9.0"
+
 asn1.js@^5.2.0:
 asn1.js@^5.2.0:
   version "5.4.1"
   version "5.4.1"
   resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
   resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07"
@@ -25,6 +37,11 @@ available-typed-arrays@^1.0.2:
   resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
   resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
   integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
   integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
 
 
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
 base64-js@^1.3.1:
 base64-js@^1.3.1:
   version "1.5.1"
   version "1.5.1"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
   resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@@ -40,6 +57,21 @@ bn.js@^5.0.0, bn.js@^5.1.1:
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
   resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
   integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
   integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
 
 
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+braces@^3.0.1:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
 brorand@^1.0.1, brorand@^1.1.0:
 brorand@^1.0.1, brorand@^1.1.0:
   version "1.1.0"
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
   resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -127,6 +159,20 @@ call-bind@^1.0.0, call-bind@^1.0.2:
     function-bind "^1.1.1"
     function-bind "^1.1.1"
     get-intrinsic "^1.0.2"
     get-intrinsic "^1.0.2"
 
 
+chalk@^2.4.2:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
+ci-info@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
+  integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -135,16 +181,28 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
     inherits "^2.0.1"
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
 
 
+color-convert@^1.9.0:
+  version "1.9.3"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+  integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+  dependencies:
+    color-name "1.1.3"
+
[email protected]:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+  integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
[email protected]:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
 constants-browserify@^1.0.0:
 constants-browserify@^1.0.0:
   version "1.0.0"
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
   integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
   integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
 
 
-copy-text-to-clipboard@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
-  integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
-
 create-ecdh@^4.0.0:
 create-ecdh@^4.0.0:
   version "4.0.4"
   version "4.0.4"
   resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
   resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -176,6 +234,17 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7:
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
     sha.js "^2.4.8"
 
 
+cross-spawn@^6.0.5:
+  version "6.0.5"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
+  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
+  dependencies:
+    nice-try "^1.0.4"
+    path-key "^2.0.1"
+    semver "^5.5.0"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
 crypto-browserify@^3.12.0:
 crypto-browserify@^3.12.0:
   version "3.12.0"
   version "3.12.0"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
   resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -266,6 +335,11 @@ es-to-primitive@^1.2.1:
     is-date-object "^1.0.1"
     is-date-object "^1.0.1"
     is-symbol "^1.0.2"
     is-symbol "^1.0.2"
 
 
+escape-string-regexp@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+  integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
 events@^3.3.0:
 events@^3.3.0:
   version "3.3.0"
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
   resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
@@ -279,11 +353,39 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
     md5.js "^1.3.4"
     md5.js "^1.3.4"
     safe-buffer "^5.1.1"
     safe-buffer "^5.1.1"
 
 
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+find-yarn-workspace-root@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz#f47fb8d239c900eb78179aa81b66673eac88f7bd"
+  integrity sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==
+  dependencies:
+    micromatch "^4.0.2"
+
 foreach@^2.0.5:
 foreach@^2.0.5:
   version "2.0.5"
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
   resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
   integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
   integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
 
 
+fs-extra@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+  integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
+  dependencies:
+    graceful-fs "^4.1.2"
+    jsonfile "^4.0.0"
+    universalify "^0.1.0"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
 function-bind@^1.1.1:
 function-bind@^1.1.1:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -298,11 +400,33 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1:
     has "^1.0.3"
     has "^1.0.3"
     has-symbols "^1.0.1"
     has-symbols "^1.0.1"
 
 
+glob@^7.1.3:
+  version "7.1.7"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6:
+  version "4.2.6"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
+  integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
+
 has-bigints@^1.0.1:
 has-bigints@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
   integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
   integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
 
 
+has-flag@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+  integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
 has-symbols@^1.0.1, has-symbols@^1.0.2:
 has-symbols@^1.0.1, has-symbols@^1.0.2:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
@@ -346,7 +470,15 @@ ieee754@^1.2.1:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
 
-inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.4:
   version "2.0.4"
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -375,11 +507,23 @@ is-callable@^1.1.4, is-callable@^1.2.3:
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
   integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
   integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
 
 
+is-ci@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+  integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+  dependencies:
+    ci-info "^2.0.0"
+
 is-date-object@^1.0.1:
 is-date-object@^1.0.1:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
   resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
   integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
   integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
 
 
+is-docker@^2.0.0:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+  integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
+
 is-generator-function@^1.0.7:
 is-generator-function@^1.0.7:
   version "1.0.9"
   version "1.0.9"
   resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.9.tgz#e5f82c2323673e7fcad3d12858c83c4039f6399c"
   resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.9.tgz#e5f82c2323673e7fcad3d12858c83c4039f6399c"
@@ -395,6 +539,11 @@ is-number-object@^1.0.4:
   resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
   resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
   integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==
   integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==
 
 
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
 is-regex@^1.1.3:
 is-regex@^1.1.3:
   version "1.1.3"
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
@@ -426,6 +575,32 @@ is-typed-array@^1.1.3:
     foreach "^2.0.5"
     foreach "^2.0.5"
     has-symbols "^1.0.1"
     has-symbols "^1.0.1"
 
 
+is-wsl@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+  integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+  dependencies:
+    is-docker "^2.0.0"
+
+isexe@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+
+jsonfile@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+  integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
+klaw-sync@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/klaw-sync/-/klaw-sync-6.0.0.tgz#1fd2cfd56ebb6250181114f0a581167099c2b28c"
+  integrity sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==
+  dependencies:
+    graceful-fs "^4.1.11"
+
 md5.js@^1.3.4:
 md5.js@^1.3.4:
   version "1.3.5"
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
   resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -435,6 +610,14 @@ md5.js@^1.3.4:
     inherits "^2.0.1"
     inherits "^2.0.1"
     safe-buffer "^5.1.2"
     safe-buffer "^5.1.2"
 
 
+micromatch@^4.0.2:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
+  integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
+  dependencies:
+    braces "^3.0.1"
+    picomatch "^2.2.3"
+
 miller-rabin@^4.0.0:
 miller-rabin@^4.0.0:
   version "4.0.1"
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
   resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -453,6 +636,23 @@ minimalistic-crypto-utils@^1.0.1:
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
   integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
 
 
+minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+minimist@^1.2.0:
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+  integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+nice-try@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+
 object-assign@^4.1.1:
 object-assign@^4.1.1:
   version "4.1.1"
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -478,6 +678,26 @@ object.assign@^4.1.2:
     has-symbols "^1.0.1"
     has-symbols "^1.0.1"
     object-keys "^1.1.1"
     object-keys "^1.1.1"
 
 
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
+open@^7.4.2:
+  version "7.4.2"
+  resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321"
+  integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==
+  dependencies:
+    is-docker "^2.0.0"
+    is-wsl "^2.1.1"
+
+os-tmpdir@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+  integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+
 pako@~1.0.5:
 pako@~1.0.5:
   version "1.0.11"
   version "1.0.11"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
   resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
@@ -494,11 +714,40 @@ parse-asn1@^5.0.0, parse-asn1@^5.1.5:
     pbkdf2 "^3.0.3"
     pbkdf2 "^3.0.3"
     safe-buffer "^5.1.1"
     safe-buffer "^5.1.1"
 
 
+patch-package@^6.4.7:
+  version "6.4.7"
+  resolved "https://registry.yarnpkg.com/patch-package/-/patch-package-6.4.7.tgz#2282d53c397909a0d9ef92dae3fdeb558382b148"
+  integrity sha512-S0vh/ZEafZ17hbhgqdnpunKDfzHQibQizx9g8yEf5dcVk3KOflOfdufRXQX8CSEkyOQwuM/bNz1GwKvFj54kaQ==
+  dependencies:
+    "@yarnpkg/lockfile" "^1.1.0"
+    chalk "^2.4.2"
+    cross-spawn "^6.0.5"
+    find-yarn-workspace-root "^2.0.0"
+    fs-extra "^7.0.1"
+    is-ci "^2.0.0"
+    klaw-sync "^6.0.0"
+    minimist "^1.2.0"
+    open "^7.4.2"
+    rimraf "^2.6.3"
+    semver "^5.6.0"
+    slash "^2.0.0"
+    tmp "^0.0.33"
+
 path-browserify@^1.0.1:
 path-browserify@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
   resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
   integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
   integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
 
 
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+path-key@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+  integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+
 pbkdf2@^3.0.3:
 pbkdf2@^3.0.3:
   version "3.1.2"
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
   resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
@@ -510,6 +759,11 @@ pbkdf2@^3.0.3:
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
     sha.js "^2.4.8"
     sha.js "^2.4.8"
 
 
+picomatch@^2.2.3:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
+  integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
+
 public-encrypt@^4.0.0:
 public-encrypt@^4.0.0:
   version "4.0.3"
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
   resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0"
@@ -546,6 +800,13 @@ readable-stream@^3.5.0, readable-stream@^3.6.0:
     string_decoder "^1.1.1"
     string_decoder "^1.1.1"
     util-deprecate "^1.0.1"
     util-deprecate "^1.0.1"
 
 
+rimraf@^2.6.3:
+  version "2.7.1"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
+  integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
+  dependencies:
+    glob "^7.1.3"
+
 ripemd160@^2.0.0, ripemd160@^2.0.1:
 ripemd160@^2.0.0, ripemd160@^2.0.1:
   version "2.0.2"
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
   resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -564,6 +825,11 @@ safer-buffer@^2.1.0:
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
 
+semver@^5.5.0, semver@^5.6.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
 sha.js@^2.4.0, sha.js@^2.4.8:
 sha.js@^2.4.0, sha.js@^2.4.8:
   version "2.4.11"
   version "2.4.11"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
   resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
@@ -572,6 +838,23 @@ sha.js@^2.4.0, sha.js@^2.4.8:
     inherits "^2.0.1"
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
     safe-buffer "^5.0.1"
 
 
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
+  dependencies:
+    shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+  integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+
+slash@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
+  integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+
 stream-browserify@^3.0.0:
 stream-browserify@^3.0.0:
   version "3.0.0"
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
   resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
@@ -603,6 +886,27 @@ string_decoder@^1.1.1:
   dependencies:
   dependencies:
     safe-buffer "~5.2.0"
     safe-buffer "~5.2.0"
 
 
+supports-color@^5.3.0:
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+  dependencies:
+    has-flag "^3.0.0"
+
+tmp@^0.0.33:
+  version "0.0.33"
+  resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+  integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+  dependencies:
+    os-tmpdir "~1.0.2"
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
 unbox-primitive@^1.0.1:
 unbox-primitive@^1.0.1:
   version "1.0.1"
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
   resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
@@ -613,6 +917,11 @@ unbox-primitive@^1.0.1:
     has-symbols "^1.0.2"
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
 
+universalify@^0.1.0:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+  integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
 util-deprecate@^1.0.1:
 util-deprecate@^1.0.1:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -653,3 +962,15 @@ which-typed-array@^1.1.2:
     function-bind "^1.1.1"
     function-bind "^1.1.1"
     has-symbols "^1.0.1"
     has-symbols "^1.0.1"
     is-typed-array "^1.1.3"
     is-typed-array "^1.1.3"
+
+which@^1.2.9:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+  dependencies:
+    isexe "^2.0.0"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=