├── .github └── workflows │ ├── build.yml │ └── qodana_code_quality.yml ├── .gitignore ├── .idea ├── .gitignore ├── .idea.OpenSSHA-GUI │ └── .idea │ │ ├── .name │ │ ├── projectSettingsUpdater.xml │ │ └── workspace.xml ├── .idea.OpenSSH_GUI │ └── .idea │ │ ├── avalonia.xml │ │ ├── dataSources.local.xml │ │ ├── dataSources.xml │ │ ├── dataSources │ │ ├── d6fa5daa-3f37-4b1d-80de-189ee8b27c70.corrupted.20241119-114550.reason.txt │ │ ├── d6fa5daa-3f37-4b1d-80de-189ee8b27c70.corrupted.20241119-114550.xml │ │ ├── d6fa5daa-3f37-4b1d-80de-189ee8b27c70.xml │ │ └── d6fa5daa-3f37-4b1d-80de-189ee8b27c70 │ │ │ └── storage_v2 │ │ │ └── _src_ │ │ │ └── schema │ │ │ └── main.uQUzAA.meta │ │ ├── deployment.xml │ │ ├── git_toolbox_blame.xml │ │ ├── git_toolbox_prj.xml │ │ ├── indexLayout.xml │ │ ├── projectSettingsUpdater.xml │ │ ├── shelf │ │ ├── Uncommitted_changes_before_Merge_at_12_06_2024_12_45_[Changes] │ │ │ └── shelved.patch │ │ ├── Uncommitted_changes_before_Merge_at_12_06_2024_12_45__Changes_.xml │ │ ├── Uncommitted_changes_before_Update_at_11_06_2024_10_09_[Changes] │ │ │ └── shelved.patch │ │ ├── Uncommitted_changes_before_Update_at_11_06_2024_10_09__Changes_.xml │ │ ├── Uncommitted_changes_before_Update_at_21_05_2024_08_03_[Changes] │ │ │ └── shelved.patch │ │ ├── Uncommitted_changes_before_Update_at_21_05_2024_08_03__Changes_.xml │ │ └── Uncommitted_changes_before_rebase_[Changes] │ │ │ ├── AddKeyWindow.png │ │ │ ├── AppSettings.png │ │ │ ├── ConnectToServerQuickConnect.png │ │ │ ├── ConnectToServerWindowWithKey.png │ │ │ ├── FoundPasswordProtectedKey.png │ │ │ ├── MainWindow.png │ │ │ ├── NewMainUI.png │ │ │ ├── ProvidePasswordPrompt.png │ │ │ ├── SettingsContextMenu.png │ │ │ ├── ShowForgetPws.png │ │ │ └── Sorted.png │ │ ├── vcs.xml │ │ └── workspace.xml ├── avalonia.xml ├── deployment.xml ├── encodings.xml ├── git_toolbox_prj.xml ├── indexLayout.xml ├── misc.xml ├── riderPublish.xml └── vcs.xml ├── LICENSE ├── OpenSSHA-GUI.sln.DotSettings.user ├── OpenSSH_GUI.Core ├── Database │ ├── Context │ │ └── OpenSshGuiDbContext.cs │ ├── DTO │ │ ├── ConnectionCredentialsDto.cs │ │ └── SshKeyDto.cs │ └── Migrations │ │ ├── 20240521113602_Initial.Designer.cs │ │ ├── 20240521113602_Initial.cs │ │ └── OpenSshGuiDbContextModelSnapshot.cs ├── Enums │ ├── AuthType.cs │ ├── EncryptionType.cs │ ├── KeyType.cs │ ├── SshConfigFiles.cs │ └── UnixPermissions.cs ├── Extensions │ ├── FileStreamExtensions.cs │ ├── IConnectionCredentialsExtension.cs │ ├── IPrivateKeySourceExtensions.cs │ ├── KeyTypeExtension.cs │ ├── SshConfigFilesExtension.cs │ ├── SshKeyFormatExtension.cs │ ├── SshKeyGenerateParamsExtensions.cs │ └── StringExtensions.cs ├── Interfaces │ ├── AuthorizedKeys │ │ ├── IAuthorizedKey.cs │ │ └── IAuthorizedKeysFile.cs │ ├── Credentials │ │ ├── IConnectionCredentials.cs │ │ ├── IKeyConnectionCredentials.cs │ │ ├── IMultiKeyConnectionCredentials.cs │ │ └── IPasswordConnectionCredentials.cs │ ├── Keys │ │ ├── IPpkKey.cs │ │ ├── ISshKey.cs │ │ ├── ISshKeyType.cs │ │ ├── ISshPrivateKey.cs │ │ └── ISshPublicKey.cs │ ├── KnownHosts │ │ ├── IKnownHost.cs │ │ ├── IKnownHostKey.cs │ │ └── IKnownHostsFile.cs │ └── Misc │ │ ├── IKeyBase.cs │ │ └── IServerConnection.cs ├── Lib │ ├── Abstract │ │ └── KeyBase.cs │ ├── AuthorizedKeys │ │ ├── AuthorizedKey.cs │ │ └── AuthorizedKeysFile.cs │ ├── Credentials │ │ ├── ConnectionCredentials.cs │ │ ├── KeyConnectionCredentials.cs │ │ ├── MultiKeyConnectionCredentials.cs │ │ └── PasswordConnectionCredentials.cs │ ├── Keys │ │ ├── PpkKey.cs │ │ ├── SSHKey.cs │ │ ├── SSHKeyType.cs │ │ ├── SshPrivateKey.cs │ │ └── SshPublicKey.cs │ ├── KnownHosts │ │ ├── KnownHost.cs │ │ ├── KnownHostKey.cs │ │ └── KnownHostsFile.cs │ ├── Misc │ │ ├── DirectoryCrawler.cs │ │ ├── ServerConnection.cs │ │ └── SshKeyGenerateParams.cs │ ├── Settings │ │ └── Settings.cs │ └── Static │ │ ├── FileOperations.cs │ │ └── KeyFactory.cs └── OpenSSH_GUI.Core.csproj ├── OpenSSH_GUI.Tests ├── OpenSSH_GUI.Tests.csproj └── UnitTest1.cs ├── OpenSSH_GUI.sln ├── OpenSSH_GUI.sln.DotSettings ├── OpenSSH_GUI.sln.DotSettings.user ├── OpenSSH_GUI ├── App.axaml ├── App.axaml.cs ├── Assets │ ├── appicon.ico │ ├── appicon.png │ └── avalonia-logo.ico ├── Converters │ ├── PlatformIdConverter.cs │ ├── SingleSshKeyTypeConverter.cs │ └── SshKeyTypeConverter.cs ├── OpenSSH_GUI.csproj ├── Program.cs ├── Resources │ ├── Controls │ │ ├── BottomButtons.axaml │ │ └── BottomButtons.axaml.cs │ ├── Styles │ │ └── ISshKeyStyles.axaml │ └── Wrapper │ │ ├── WindowBase.cs │ │ └── WindowInteraction.cs ├── StringsAndTexts.Designer.cs ├── StringsAndTexts.de.resx ├── StringsAndTexts.resx ├── ViewLocator.cs ├── ViewModels │ ├── AddKeyWindowViewModel.cs │ ├── ApplicationSettingsViewModel.cs │ ├── ConnectToServerViewModel.cs │ ├── EditAuthorizedKeysViewModel.cs │ ├── EditKnownHostsViewModel.cs │ ├── EditSavedServerEntryViewModel.cs │ ├── ExportWindowViewModel.cs │ ├── MainWindowViewModel.cs │ └── ViewModelBase.cs ├── Views │ ├── AddKeyWindow.axaml │ ├── AddKeyWindow.axaml.cs │ ├── ApplicationSettingsWindow.axaml │ ├── ApplicationSettingsWindow.axaml.cs │ ├── ConnectToServerWindow.axaml │ ├── ConnectToServerWindow.axaml.cs │ ├── EditAuthorizedKeysWindow.axaml │ ├── EditAuthorizedKeysWindow.axaml.cs │ ├── EditKnownHostsWindow.axaml │ ├── EditKnownHostsWindow.axaml.cs │ ├── EditSavedServerEntry.axaml │ ├── EditSavedServerEntry.axaml.cs │ ├── ExportWindow.axaml │ ├── ExportWindow.axaml.cs │ ├── MainWindow.axaml │ └── MainWindow.axaml.cs └── app.manifest ├── README.md ├── images ├── AddKeyWindow.png ├── AppSettings.png ├── ConnectToServerQuickConnect.png ├── ConnectToServerWindow.png ├── ConnectToServerWindowSuccess.png ├── ConnectToServerWindowWithKey.png ├── EditAuthorizedKeysWindow.png ├── EditAuthorizedKeysWindowRemote.png ├── ExportKeyWindow.png ├── FoundPasswordProtectedKey.png ├── KnownHostsWindow.png ├── MainWindow.png ├── NewMainUI.png ├── ProvidePasswordPrompt.png ├── SettingsContextMenu.png ├── ShowForgetPws.png ├── Sorted.png ├── tooltip.png ├── tooltipKey.png └── tooltipServer.png ├── nuget.config └── qodana.yaml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | push: 7 | tags: 8 | - '*' 9 | 10 | jobs: 11 | build_and_release: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v2 17 | 18 | - name: Cache dependencies 19 | uses: actions/cache@v2 20 | with: 21 | path: | 22 | ~/.nuget/packages 23 | ~/.dotnet 24 | key: ${{ runner.os }}-dotnet-${{ hashFiles('**/*.csproj') }} 25 | restore-keys: | 26 | ${{ runner.os }}-dotnet- 27 | 28 | - name: Setup .NET SDK 29 | uses: actions/setup-dotnet@v4 30 | with: 31 | dotnet-version: 8.0.x 32 | 33 | - name: Build for Linux 34 | run: dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true OpenSSH_GUI/OpenSSH_GUI.csproj 35 | 36 | - name: Build for Windows 37 | run: dotnet publish -c Release -r win-x64 --self-contained true -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true OpenSSH_GUI/OpenSSH_GUI.csproj 38 | 39 | - name: Build for macOS 40 | run: dotnet publish -c Release -r osx-x64 --self-contained true -p:PublishReadyToRun=true -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true OpenSSH_GUI/OpenSSH_GUI.csproj 41 | 42 | - name: Create Release 43 | id: create_release 44 | uses: actions/create-release@v1 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 47 | with: 48 | tag_name: ${{ github.ref }} 49 | release_name: Release ${{ github.ref }} 50 | draft: false 51 | prerelease: false 52 | 53 | - name: Upload Linux release to Release 54 | uses: actions/upload-release-asset@v1 55 | env: 56 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 57 | with: 58 | upload_url: ${{ steps.create_release.outputs.upload_url }} 59 | asset_path: OpenSSH_GUI/bin/Release/net8.0/linux-x64/publish/OpenSSH_GUI 60 | asset_name: OpenSSH-GUI-linux-x64.bin 61 | asset_content_type: application/x-openssh-gui 62 | 63 | - name: Upload Windows release to Release 64 | uses: actions/upload-release-asset@v1 65 | env: 66 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 67 | with: 68 | upload_url: ${{ steps.create_release.outputs.upload_url }} 69 | asset_path: OpenSSH_GUI/bin/Release/net8.0/win-x64/publish/OpenSSH_GUI.exe 70 | asset_name: OpenSSH-GUI-win-x64.exe 71 | asset_content_type: application/x-openssh-gui 72 | 73 | - name: Upload macOS release to Release 74 | uses: actions/upload-release-asset@v1 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 77 | with: 78 | upload_url: ${{ steps.create_release.outputs.upload_url }} 79 | asset_path: OpenSSH_GUI/bin/Release/net8.0/osx-x64/publish/OpenSSH_GUI 80 | asset_name: OpenSSH-GUI-osx-x64.bin 81 | asset_content_type: application/x-openssh-gui 82 | -------------------------------------------------------------------------------- /.github/workflows/qodana_code_quality.yml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | push: 6 | branches: # Specify your branches here 7 | - main # The 'main' branch 8 | - 'releases/*' # The release branches 9 | 10 | jobs: 11 | qodana: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | pull-requests: write 16 | checks: write 17 | steps: 18 | - uses: actions/checkout@v3 19 | with: 20 | ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit 21 | fetch-depth: 0 # a full history is required for pull request analysis 22 | - name: 'Qodana Scan' 23 | uses: JetBrains/qodana-action@v2024.1 24 | with: 25 | pr-mode: false 26 | env: 27 | QODANA_TOKEN: ${{ secrets.QODANA_TOKEN_1539972718 }} 28 | QODANA_ENDPOINT: 'https://qodana.cloud' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /contentModel.xml 6 | /projectSettingsUpdater.xml 7 | /.idea.OpenSSH_GUI.iml 8 | /modules.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSHA-GUI/.idea/.name: -------------------------------------------------------------------------------- 1 | OpenSSHA-GUI -------------------------------------------------------------------------------- /.idea/.idea.OpenSSHA-GUI/.idea/projectSettingsUpdater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/avalonia.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/dataSources.local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | " 7 | 8 | 9 | no-auth 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/dataSources.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sqlite.xerial 6 | true 7 | org.sqlite.JDBC 8 | jdbc:sqlite:$USER_HOME$/AppData/Roaming/OpenSSH_GUI/OpenSSH_GUI 9 | 10 | 11 | 12 | $ProjectFileDir$ 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/dataSources/d6fa5daa-3f37-4b1d-80de-189ee8b27c70.corrupted.20241119-114550.reason.txt: -------------------------------------------------------------------------------- 1 | com.thoughtworks.xstream.io.StreamException: 2 | at com.thoughtworks.xstream.io.xml.XppReader.pullNextEvent(XppReader.java:124) 3 | at com.thoughtworks.xstream.io.xml.AbstractPullReader.readRealEvent(AbstractPullReader.java:148) 4 | at com.thoughtworks.xstream.io.xml.AbstractPullReader.readEvent(AbstractPullReader.java:135) 5 | at com.thoughtworks.xstream.io.xml.AbstractPullReader.hasMoreChildren(AbstractPullReader.java:87) 6 | at com.intellij.database.model.serialization.FixHierarchicalStreamReader.hasMoreChildren(FixHierarchicalStream.kt) 7 | at com.intellij.database.model.serialization.ModelImporter.deserializeModelHierarchy(ModelImporter.java:147) 8 | at com.intellij.database.model.serialization.ModelImporter.deserialize(ModelImporter.java:76) 9 | at com.intellij.database.dataSource.DataSourceModelStorageImpl$Companion.readModel(DataSourceModelStorageImpl.kt:605) 10 | at com.intellij.database.dataSource.DataSourceModelStorageImpl$Companion.readModel(DataSourceModelStorageImpl.kt:588) 11 | at com.intellij.database.dataSource.DataSourceModelStorageImpl.readModel(DataSourceModelStorageImpl.kt:373) 12 | at com.intellij.database.dataSource.DataSourceModelStorageImpl.loadModels(DataSourceModelStorageImpl.kt:262) 13 | at com.intellij.database.dataSource.DataSourceModelStorageImpl.readStateHeavy(DataSourceModelStorageImpl.kt:161) 14 | at com.intellij.database.dataSource.DataSourceModelStorageImpl.continueLoadingWhenInitialized$lambda$8$lambda$7$lambda$6$lambda$5(DataSourceModelStorageImpl.kt:144) 15 | at com.intellij.openapi.progress.util.BackgroundTaskUtil.lambda$runUnderDisposeAwareIndicator$15(BackgroundTaskUtil.java:371) 16 | at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$1(CoreProgressManager.java:223) 17 | at com.intellij.platform.diagnostic.telemetry.helpers.TraceKt.use(trace.kt:45) 18 | at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:222) 19 | at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$14(CoreProgressManager.java:674) 20 | at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:749) 21 | at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:705) 22 | at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:673) 23 | at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:79) 24 | at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:203) 25 | at com.intellij.openapi.progress.util.BackgroundTaskUtil.runUnderDisposeAwareIndicator(BackgroundTaskUtil.java:366) 26 | at com.intellij.openapi.progress.util.BackgroundTaskUtil.runUnderDisposeAwareIndicator(BackgroundTaskUtil.java:349) 27 | at com.intellij.database.dataSource.DataSourceModelStorageImpl.continueLoadingWhenInitialized$lambda$8$lambda$7$lambda$6(DataSourceModelStorageImpl.kt:143) 28 | at com.intellij.database.util.AsyncTask$Frame$compute$$inlined$supply$1$1.run(AsyncTask.kt:338) 29 | at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$14(CoreProgressManager.java:674) 30 | at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:749) 31 | at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:705) 32 | at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:673) 33 | at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:79) 34 | at com.intellij.database.util.AsyncTask$Frame$compute$$inlined$supply$1.get(AsyncTask.kt:900) 35 | at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768) 36 | at com.intellij.util.concurrency.ChildContext$runInChildContext$1.invoke(propagation.kt:101) 37 | at com.intellij.util.concurrency.ChildContext$runInChildContext$1.invoke(propagation.kt:101) 38 | at com.intellij.util.concurrency.ChildContext.runInChildContext(propagation.kt:107) 39 | at com.intellij.util.concurrency.ChildContext.runInChildContext(propagation.kt:101) 40 | at com.intellij.util.concurrency.ContextRunnable.run(ContextRunnable.java:27) 41 | at com.intellij.database.dataSource.DataSourceModelStorageImpl$continueLoadingWhenInitialized$2$1$1$1$1.invokeSuspend(DataSourceModelStorageImpl.kt:142) 42 | at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 43 | at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) 44 | at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:608) 45 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:873) 46 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:763) 47 | at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:750) 48 | Caused by: org.xmlpull.v1.XmlPullParserException: unexpected character in markup < (position: END_TAG seen ...\r\n \r\n<<... @7:3) 49 | at io.github.xstream.mxparser.MXParser.nextImpl(MXParser.java:1273) 50 | at io.github.xstream.mxparser.MXParser.next(MXParser.java:1104) 51 | at com.thoughtworks.xstream.io.xml.XppReader.pullNextEvent(XppReader.java:109) 52 | ... 45 more 53 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/dataSources/d6fa5daa-3f37-4b1d-80de-189ee8b27c70/storage_v2/_src_/schema/main.uQUzAA.meta: -------------------------------------------------------------------------------- 1 | #n:main 2 | ! [0, 0, null, null, -2147483648, -2147483648] 3 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/git_toolbox_blame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/projectSettingsUpdater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_Merge_at_12_06_2024_12_45__Changes_.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_Update_at_11_06_2024_10_09__Changes_.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_Update_at_21_05_2024_08_03__Changes_.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/AddKeyWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/AddKeyWindow.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/AppSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/AppSettings.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ConnectToServerQuickConnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ConnectToServerQuickConnect.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ConnectToServerWindowWithKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ConnectToServerWindowWithKey.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/FoundPasswordProtectedKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/FoundPasswordProtectedKey.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/MainWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/MainWindow.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/NewMainUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/NewMainUI.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ProvidePasswordPrompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ProvidePasswordPrompt.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/SettingsContextMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/SettingsContextMenu.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ShowForgetPws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/ShowForgetPws.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/Sorted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/.idea/.idea.OpenSSH_GUI/.idea/shelf/Uncommitted_changes_before_rebase_[Changes]/Sorted.png -------------------------------------------------------------------------------- /.idea/.idea.OpenSSH_GUI/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/avalonia.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/git_toolbox_prj.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/riderPublish.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Oliver Schantz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /OpenSSHA-GUI.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | <SessionState ContinuousTestingMode="0" IsActive="True" Name="TestCommand" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 3 | <TestAncestor> 4 | <TestId>NUnit3x::2CFEA7E7-DA50-446C-8934-210FB4342B77::net8.0::OpenSSHA_GUITests.Tests.Shrug</TestId> 5 | </TestAncestor> 6 | </SessionState> -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/Context/OpenSshGuiDbContext.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 17.05.2024 - 08:05:28 3 | // Last edit: 17.05.2024 - 08:05:29 4 | 5 | using Microsoft.Data.Sqlite; 6 | using Microsoft.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore.DataEncryption; 8 | using Microsoft.EntityFrameworkCore.DataEncryption.Providers; 9 | using OpenSSH_GUI.Core.Database.DTO; 10 | using OpenSSH_GUI.Core.Lib.Settings; 11 | 12 | namespace OpenSSH_GUI.Core.Database.Context; 13 | 14 | /// 15 | /// Represents the database context for the OpenSSH GUI application. 16 | /// 17 | public class OpenSshGuiDbContext : DbContext 18 | { 19 | /// 20 | /// Represents the initialization vector (IV) used for data encryption in the OpenSshGuiDbContext class. 21 | /// 22 | private readonly byte[] _encyptionIV = Convert.FromBase64String("lWXnTKuRPnieE8nzpyl1Gg=="); 23 | 24 | /// 25 | /// Represents the encryption key used for data encryption in the OpenSshGuiDbContext class. 26 | /// 27 | private readonly byte[] _encyptionKey = Convert.FromBase64String("Jk7JqD9mAafdvTvhNESHkXBFdy7phfyDR0FsnyGw2nY="); 28 | 29 | /// 30 | /// Represents an encryption provider used by the OpenSshGuiDbContext. 31 | /// 32 | private IEncryptionProvider _provider => new AesProvider(_encyptionKey, _encyptionIV); 33 | 34 | 35 | /// 36 | /// Represents a settings file for the application. 37 | /// 38 | public virtual DbSet Settings { get; set; } 39 | 40 | /// 41 | /// Represents a SSH key data transfer object (DTO). 42 | /// 43 | public virtual DbSet KeyDtos { get; set; } 44 | 45 | /// 46 | /// Represents a data transfer object (DTO) for connection credentials. 47 | /// 48 | public virtual DbSet ConnectionCredentialsDtos { get; set; } 49 | 50 | /// 51 | /// Configures the database connection for the OpenSshGuiDbContext. 52 | /// 53 | /// The options builder used to configure the database connection. 54 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 55 | { 56 | var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 57 | AppDomain.CurrentDomain.FriendlyName); 58 | if (!Directory.Exists(appDataPath)) Directory.CreateDirectory(appDataPath); 59 | var dbFilePath = Path.Combine(appDataPath, $"{AppDomain.CurrentDomain.FriendlyName}"); 60 | var builder = new SqliteConnectionStringBuilder 61 | { 62 | DataSource = dbFilePath, 63 | Mode = SqliteOpenMode.ReadWriteCreate, 64 | Pooling = true 65 | }; 66 | optionsBuilder.UseSqlite(builder.ToString()) 67 | .UseLazyLoadingProxies(); 68 | base.OnConfiguring(optionsBuilder); 69 | } 70 | 71 | /// 72 | /// This method is called when the model for a derived context has been initialized, but before 73 | /// the model has been locked down and used to initialize the context. It allows further 74 | /// modification of the model before it is locked down. 75 | /// 76 | /// The builder that defines the model for the context. 77 | protected override void OnModelCreating(ModelBuilder modelBuilder) 78 | { 79 | modelBuilder.UseEncryption(_provider); 80 | 81 | modelBuilder.Entity().HasKey(e => e.Version); 82 | 83 | modelBuilder.Entity().HasKey(e => e.Id); 84 | modelBuilder.Entity() 85 | .HasIndex(e => e.AbsolutePath).IsUnique(); 86 | 87 | modelBuilder.Entity() 88 | .HasMany(e => e.ConnectionCredentialsDto) 89 | .WithMany(e => e.KeyDtos); 90 | 91 | modelBuilder.Entity().HasKey(e => e.Id); 92 | modelBuilder.Entity() 93 | .HasMany(e => e.KeyDtos) 94 | .WithMany(e => e.ConnectionCredentialsDto); 95 | } 96 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/DTO/ConnectionCredentialsDto.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 18.05.2024 - 16:05:59 3 | // Last edit: 18.05.2024 - 16:05:59 4 | 5 | using System.Collections.ObjectModel; 6 | using System.ComponentModel.DataAnnotations; 7 | using OpenSSH_GUI.Core.Enums; 8 | using OpenSSH_GUI.Core.Interfaces.Credentials; 9 | using OpenSSH_GUI.Core.Interfaces.Keys; 10 | using OpenSSH_GUI.Core.Lib.Credentials; 11 | 12 | namespace OpenSSH_GUI.Core.Database.DTO; 13 | 14 | /// 15 | /// Represents a data transfer object (DTO) for connection credentials. 16 | /// 17 | public class ConnectionCredentialsDto 18 | { 19 | /// 20 | /// Represents the property Id of a connection credential. 21 | /// 22 | [Key] 23 | public int Id { get; set; } 24 | 25 | /// 26 | /// Represents the hostname of a connection. 27 | /// 28 | [Encrypted] 29 | public string Hostname { get; set; } 30 | 31 | /// 32 | /// Represents the username used for connection credentials. 33 | /// 34 | [Encrypted] 35 | public string Username { get; set; } 36 | 37 | /// 38 | /// The port number for the SSH connection. 39 | /// 40 | public int Port { get; set; } 41 | 42 | /// 43 | /// Represents the authentication type for a connection. 44 | /// 45 | public AuthType AuthType { get; set; } 46 | 47 | /// 48 | /// Represents a data transfer object for connection credentials. 49 | /// 50 | public virtual List KeyDtos { get; set; } 51 | 52 | /// 53 | /// Represents the password for a connection. 54 | /// 55 | [Encrypted] 56 | public string? Password { get; set; } 57 | 58 | /// 59 | /// Gets or sets a flag indicating whether the password is encrypted. 60 | /// 61 | public bool PasswordEncrypted { get; set; } 62 | 63 | /// 64 | /// Converts a ConnectionCredentialsDto object to an instance of IConnectionCredentials. 65 | /// 66 | /// The converted IConnectionCredentials object. 67 | public IConnectionCredentials ToCredentials() 68 | { 69 | var g = KeyDtos.Select(e => e.ToKey()); 70 | return ToCredentials(ref g); 71 | } 72 | 73 | public IConnectionCredentials ToCredentials(ref ObservableCollection keys) 74 | { 75 | var g = keys.Select(e => e); 76 | return ToCredentials(ref g); 77 | } 78 | 79 | public IConnectionCredentials ToCredentials(ref IEnumerable keys) 80 | { 81 | return AuthType switch 82 | { 83 | AuthType.Key => new KeyConnectionCredentials(Hostname, Username, KeyDtos.First().ToKey()) { Id = Id }, 84 | AuthType.Password => new PasswordConnectionCredentials(Hostname, Username, Password, 85 | PasswordEncrypted) { Id = Id }, 86 | AuthType.MultiKey => new MultiKeyConnectionCredentials(Hostname, Username, 87 | keys.Where(e => KeyDtos.All(f => f.Id != e.Id))) { Id = Id } 88 | }; 89 | } 90 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/DTO/SshKeyDto.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 17.05.2024 - 08:05:19 3 | // Last edit: 17.05.2024 - 08:05:19 4 | 5 | using System.ComponentModel.DataAnnotations; 6 | using OpenSSH_GUI.Core.Interfaces.Keys; 7 | using OpenSSH_GUI.Core.Lib.Static; 8 | using SshNet.Keygen; 9 | 10 | namespace OpenSSH_GUI.Core.Database.DTO; 11 | 12 | /// 13 | /// Represents a SSH key data transfer object (DTO). 14 | /// 15 | public class SshKeyDto 16 | { 17 | /// 18 | /// Gets or sets the ID of the SSH key. 19 | /// 20 | /// 21 | /// The ID uniquely identifies the SSH key. 22 | /// 23 | [Key] 24 | public int Id { get; set; } 25 | 26 | /// 27 | /// Gets or sets the absolute path of the SSH key. 28 | /// 29 | public string AbsolutePath { get; set; } 30 | 31 | /// 32 | /// Represents the format of an SSH key. 33 | /// 34 | public SshKeyFormat Format { get; set; } 35 | 36 | /// 37 | /// Represents a password associated with a specific SSH key. 38 | /// 39 | [Encrypted] 40 | public string? Password { get; set; } 41 | 42 | /// 43 | /// Represents the connection credentials for SSH connection. 44 | /// 45 | public virtual IEnumerable ConnectionCredentialsDto { get; set; } 46 | 47 | /// 48 | /// Converts an instance of to an instance of . 49 | /// 50 | /// The converted instance. 51 | public ISshKey? ToKey() 52 | { 53 | return KeyFactory.FromPath(AbsolutePath, Password, Id); 54 | } 55 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/Migrations/20240521113602_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 6 | using OpenSSH_GUI.Core.Database.Context; 7 | 8 | #nullable disable 9 | 10 | namespace OpenSSH_GUI.Core.Database.Migrations 11 | { 12 | [DbContext(typeof(OpenSshGuiDbContext))] 13 | [Migration("20240521113602_Initial")] 14 | partial class Initial 15 | { 16 | /// 17 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 18 | { 19 | #pragma warning disable 612, 618 20 | modelBuilder 21 | .HasAnnotation("ProductVersion", "8.0.5") 22 | .HasAnnotation("Proxies:ChangeTracking", false) 23 | .HasAnnotation("Proxies:CheckEquality", false) 24 | .HasAnnotation("Proxies:LazyLoading", true); 25 | 26 | modelBuilder.Entity("ConnectionCredentialsDtoSshKeyDto", b => 27 | { 28 | b.Property("ConnectionCredentialsDtoId") 29 | .HasColumnType("INTEGER"); 30 | 31 | b.Property("KeyDtosId") 32 | .HasColumnType("INTEGER"); 33 | 34 | b.HasKey("ConnectionCredentialsDtoId", "KeyDtosId"); 35 | 36 | b.HasIndex("KeyDtosId"); 37 | 38 | b.ToTable("ConnectionCredentialsDtoSshKeyDto"); 39 | }); 40 | 41 | modelBuilder.Entity("OpenSSH_GUI.Core.Database.DTO.ConnectionCredentialsDto", b => 42 | { 43 | b.Property("Id") 44 | .ValueGeneratedOnAdd() 45 | .HasColumnType("INTEGER"); 46 | 47 | b.Property("AuthType") 48 | .HasColumnType("INTEGER"); 49 | 50 | b.Property("Hostname") 51 | .IsRequired() 52 | .HasColumnType("TEXT"); 53 | 54 | b.Property("Password") 55 | .HasColumnType("TEXT"); 56 | 57 | b.Property("PasswordEncrypted") 58 | .HasColumnType("INTEGER"); 59 | 60 | b.Property("Port") 61 | .HasColumnType("INTEGER"); 62 | 63 | b.Property("Username") 64 | .IsRequired() 65 | .HasColumnType("TEXT"); 66 | 67 | b.HasKey("Id"); 68 | 69 | b.ToTable("ConnectionCredentialsDtos"); 70 | }); 71 | 72 | modelBuilder.Entity("OpenSSH_GUI.Core.Database.DTO.SshKeyDto", b => 73 | { 74 | b.Property("Id") 75 | .ValueGeneratedOnAdd() 76 | .HasColumnType("INTEGER"); 77 | 78 | b.Property("AbsolutePath") 79 | .IsRequired() 80 | .HasColumnType("TEXT"); 81 | 82 | b.Property("Format") 83 | .HasColumnType("INTEGER"); 84 | 85 | b.Property("Password") 86 | .HasColumnType("TEXT"); 87 | 88 | b.HasKey("Id"); 89 | 90 | b.HasIndex("AbsolutePath") 91 | .IsUnique(); 92 | 93 | b.ToTable("KeyDtos"); 94 | }); 95 | 96 | modelBuilder.Entity("OpenSSH_GUI.Core.Lib.Settings.Settings", b => 97 | { 98 | b.Property("Version") 99 | .HasColumnType("TEXT"); 100 | 101 | b.Property("ConvertPpkAutomatically") 102 | .HasColumnType("INTEGER"); 103 | 104 | b.HasKey("Version"); 105 | 106 | b.ToTable("Settings"); 107 | }); 108 | 109 | modelBuilder.Entity("ConnectionCredentialsDtoSshKeyDto", b => 110 | { 111 | b.HasOne("OpenSSH_GUI.Core.Database.DTO.ConnectionCredentialsDto", null) 112 | .WithMany() 113 | .HasForeignKey("ConnectionCredentialsDtoId") 114 | .OnDelete(DeleteBehavior.Cascade) 115 | .IsRequired(); 116 | 117 | b.HasOne("OpenSSH_GUI.Core.Database.DTO.SshKeyDto", null) 118 | .WithMany() 119 | .HasForeignKey("KeyDtosId") 120 | .OnDelete(DeleteBehavior.Cascade) 121 | .IsRequired(); 122 | }); 123 | #pragma warning restore 612, 618 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/Migrations/20240521113602_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace OpenSSH_GUI.Core.Database.Migrations 6 | { 7 | /// 8 | public partial class Initial : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.CreateTable( 14 | name: "ConnectionCredentialsDtos", 15 | columns: table => new 16 | { 17 | Id = table.Column(type: "INTEGER", nullable: false) 18 | .Annotation("Sqlite:Autoincrement", true), 19 | Hostname = table.Column(type: "TEXT", nullable: false), 20 | Username = table.Column(type: "TEXT", nullable: false), 21 | Port = table.Column(type: "INTEGER", nullable: false), 22 | AuthType = table.Column(type: "INTEGER", nullable: false), 23 | Password = table.Column(type: "TEXT", nullable: true), 24 | PasswordEncrypted = table.Column(type: "INTEGER", nullable: false) 25 | }, 26 | constraints: table => 27 | { 28 | table.PrimaryKey("PK_ConnectionCredentialsDtos", x => x.Id); 29 | }); 30 | 31 | migrationBuilder.CreateTable( 32 | name: "KeyDtos", 33 | columns: table => new 34 | { 35 | Id = table.Column(type: "INTEGER", nullable: false) 36 | .Annotation("Sqlite:Autoincrement", true), 37 | AbsolutePath = table.Column(type: "TEXT", nullable: false), 38 | Format = table.Column(type: "INTEGER", nullable: false), 39 | Password = table.Column(type: "TEXT", nullable: true) 40 | }, 41 | constraints: table => 42 | { 43 | table.PrimaryKey("PK_KeyDtos", x => x.Id); 44 | }); 45 | 46 | migrationBuilder.CreateTable( 47 | name: "Settings", 48 | columns: table => new 49 | { 50 | Version = table.Column(type: "TEXT", nullable: false), 51 | ConvertPpkAutomatically = table.Column(type: "INTEGER", nullable: false) 52 | }, 53 | constraints: table => 54 | { 55 | table.PrimaryKey("PK_Settings", x => x.Version); 56 | }); 57 | 58 | migrationBuilder.CreateTable( 59 | name: "ConnectionCredentialsDtoSshKeyDto", 60 | columns: table => new 61 | { 62 | ConnectionCredentialsDtoId = table.Column(type: "INTEGER", nullable: false), 63 | KeyDtosId = table.Column(type: "INTEGER", nullable: false) 64 | }, 65 | constraints: table => 66 | { 67 | table.PrimaryKey("PK_ConnectionCredentialsDtoSshKeyDto", x => new { x.ConnectionCredentialsDtoId, x.KeyDtosId }); 68 | table.ForeignKey( 69 | name: "FK_ConnectionCredentialsDtoSshKeyDto_ConnectionCredentialsDtos_ConnectionCredentialsDtoId", 70 | column: x => x.ConnectionCredentialsDtoId, 71 | principalTable: "ConnectionCredentialsDtos", 72 | principalColumn: "Id", 73 | onDelete: ReferentialAction.Cascade); 74 | table.ForeignKey( 75 | name: "FK_ConnectionCredentialsDtoSshKeyDto_KeyDtos_KeyDtosId", 76 | column: x => x.KeyDtosId, 77 | principalTable: "KeyDtos", 78 | principalColumn: "Id", 79 | onDelete: ReferentialAction.Cascade); 80 | }); 81 | 82 | migrationBuilder.CreateIndex( 83 | name: "IX_ConnectionCredentialsDtoSshKeyDto_KeyDtosId", 84 | table: "ConnectionCredentialsDtoSshKeyDto", 85 | column: "KeyDtosId"); 86 | 87 | migrationBuilder.CreateIndex( 88 | name: "IX_KeyDtos_AbsolutePath", 89 | table: "KeyDtos", 90 | column: "AbsolutePath", 91 | unique: true); 92 | } 93 | 94 | /// 95 | protected override void Down(MigrationBuilder migrationBuilder) 96 | { 97 | migrationBuilder.DropTable( 98 | name: "ConnectionCredentialsDtoSshKeyDto"); 99 | 100 | migrationBuilder.DropTable( 101 | name: "Settings"); 102 | 103 | migrationBuilder.DropTable( 104 | name: "ConnectionCredentialsDtos"); 105 | 106 | migrationBuilder.DropTable( 107 | name: "KeyDtos"); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Database/Migrations/OpenSshGuiDbContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | // 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Storage.ValueConversion; 5 | using OpenSSH_GUI.Core.Database.Context; 6 | 7 | #nullable disable 8 | 9 | namespace OpenSSH_GUI.Core.Database.Migrations 10 | { 11 | [DbContext(typeof(OpenSshGuiDbContext))] 12 | partial class OpenSshGuiDbContextModelSnapshot : ModelSnapshot 13 | { 14 | protected override void BuildModel(ModelBuilder modelBuilder) 15 | { 16 | #pragma warning disable 612, 618 17 | modelBuilder 18 | .HasAnnotation("ProductVersion", "8.0.5") 19 | .HasAnnotation("Proxies:ChangeTracking", false) 20 | .HasAnnotation("Proxies:CheckEquality", false) 21 | .HasAnnotation("Proxies:LazyLoading", true); 22 | 23 | modelBuilder.Entity("ConnectionCredentialsDtoSshKeyDto", b => 24 | { 25 | b.Property("ConnectionCredentialsDtoId") 26 | .HasColumnType("INTEGER"); 27 | 28 | b.Property("KeyDtosId") 29 | .HasColumnType("INTEGER"); 30 | 31 | b.HasKey("ConnectionCredentialsDtoId", "KeyDtosId"); 32 | 33 | b.HasIndex("KeyDtosId"); 34 | 35 | b.ToTable("ConnectionCredentialsDtoSshKeyDto"); 36 | }); 37 | 38 | modelBuilder.Entity("OpenSSH_GUI.Core.Database.DTO.ConnectionCredentialsDto", b => 39 | { 40 | b.Property("Id") 41 | .ValueGeneratedOnAdd() 42 | .HasColumnType("INTEGER"); 43 | 44 | b.Property("AuthType") 45 | .HasColumnType("INTEGER"); 46 | 47 | b.Property("Hostname") 48 | .IsRequired() 49 | .HasColumnType("TEXT"); 50 | 51 | b.Property("Password") 52 | .HasColumnType("TEXT"); 53 | 54 | b.Property("PasswordEncrypted") 55 | .HasColumnType("INTEGER"); 56 | 57 | b.Property("Port") 58 | .HasColumnType("INTEGER"); 59 | 60 | b.Property("Username") 61 | .IsRequired() 62 | .HasColumnType("TEXT"); 63 | 64 | b.HasKey("Id"); 65 | 66 | b.ToTable("ConnectionCredentialsDtos"); 67 | }); 68 | 69 | modelBuilder.Entity("OpenSSH_GUI.Core.Database.DTO.SshKeyDto", b => 70 | { 71 | b.Property("Id") 72 | .ValueGeneratedOnAdd() 73 | .HasColumnType("INTEGER"); 74 | 75 | b.Property("AbsolutePath") 76 | .IsRequired() 77 | .HasColumnType("TEXT"); 78 | 79 | b.Property("Format") 80 | .HasColumnType("INTEGER"); 81 | 82 | b.Property("Password") 83 | .HasColumnType("TEXT"); 84 | 85 | b.HasKey("Id"); 86 | 87 | b.HasIndex("AbsolutePath") 88 | .IsUnique(); 89 | 90 | b.ToTable("KeyDtos"); 91 | }); 92 | 93 | modelBuilder.Entity("OpenSSH_GUI.Core.Lib.Settings.Settings", b => 94 | { 95 | b.Property("Version") 96 | .HasColumnType("TEXT"); 97 | 98 | b.Property("ConvertPpkAutomatically") 99 | .HasColumnType("INTEGER"); 100 | 101 | b.HasKey("Version"); 102 | 103 | b.ToTable("Settings"); 104 | }); 105 | 106 | modelBuilder.Entity("ConnectionCredentialsDtoSshKeyDto", b => 107 | { 108 | b.HasOne("OpenSSH_GUI.Core.Database.DTO.ConnectionCredentialsDto", null) 109 | .WithMany() 110 | .HasForeignKey("ConnectionCredentialsDtoId") 111 | .OnDelete(DeleteBehavior.Cascade) 112 | .IsRequired(); 113 | 114 | b.HasOne("OpenSSH_GUI.Core.Database.DTO.SshKeyDto", null) 115 | .WithMany() 116 | .HasForeignKey("KeyDtosId") 117 | .OnDelete(DeleteBehavior.Cascade) 118 | .IsRequired(); 119 | }); 120 | #pragma warning restore 612, 618 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Enums/AuthType.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Enums; 10 | 11 | /// 12 | /// Represents the types of authentication supported for SSH connections. 13 | /// 14 | public enum AuthType 15 | { 16 | /// 17 | /// Represents connection credentials using password authentication. 18 | /// 19 | Password, 20 | 21 | /// 22 | /// Represents the authentication type of connection credentials using SSH key. 23 | /// 24 | Key, 25 | 26 | /// 27 | /// Represents a multi-key authentication type for SSH connections. 28 | /// 29 | MultiKey 30 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Enums/EncryptionType.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Enums; 10 | 11 | /// 12 | /// The available encryption types for SSH keys. 13 | /// 14 | public enum EncryptionType 15 | { 16 | /// 17 | /// Represents the NONE member of the EncryptionType enum. 18 | /// 19 | NONE, 20 | 21 | /// 22 | /// Represents the RSA encryption type. 23 | /// 24 | RSA, 25 | 26 | /// 27 | /// Enumeration member representing the DSA encryption type. 28 | /// 29 | DSA, 30 | 31 | /// 32 | /// Represents the encryption type for ECDSA. 33 | /// 34 | /// 35 | /// The ECDSA encryption type is used in the OpenSSH_GUI.Core library for representing ECDSA keys. 36 | /// 37 | ECDSA, 38 | 39 | /// 40 | /// The ED25519 encryption type. 41 | /// 42 | ED25519 43 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Enums/KeyType.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Enums; 10 | 11 | /// 12 | /// Enumeration for SSH key types. 13 | /// 14 | public enum KeyType 15 | { 16 | // ReSharper disable InconsistentNaming 17 | /// 18 | /// RSA key type. 19 | /// 20 | RSA, 21 | 22 | /// 23 | /// Represents the ECDSA key type. 24 | /// 25 | ECDSA, 26 | 27 | /// 28 | /// Represents the ED25519 key type. 29 | /// 30 | ED25519 31 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Enums/SshConfigFiles.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Enums; 10 | 11 | /// 12 | /// Enumeration of SSH configuration files. 13 | /// 14 | public enum SshConfigFiles 15 | { 16 | // ReSharper disable InconsistentNaming 17 | Authorized_Keys, 18 | 19 | /// 20 | /// Represents the Known_Hosts SSH config file. 21 | /// 22 | Known_Hosts, 23 | 24 | /// 25 | /// Enumerates the SSH configuration files. 26 | /// 27 | Config, 28 | 29 | /// 30 | /// Represents the Sshd_Config option of the SshConfigFiles enumeration. 31 | /// 32 | Sshd_Config 33 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Enums/UnixPermissions.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 30.05.2024 - 11:05:16 5 | // Last edit: 30.05.2024 - 11:05:17 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Enums; 10 | 11 | public enum UnixPermissions 12 | { 13 | // Owner 14 | OwnerRead = 400, 15 | OwnerWrite = 200, 16 | OwnerExecute = 100, 17 | 18 | // Group 19 | GroupRead = 40, 20 | GroupWrite = 20, 21 | GroupExecute = 10, 22 | 23 | // Others 24 | OthersRead = 4, 25 | OthersWrite = 2, 26 | OthersExecute = 1, 27 | 28 | // Common permissions 29 | OwnerReadWrite = 600, 30 | OwnerReadWriteExecute = 700, 31 | GroupAndOthersReadExecute = 55, 32 | AllReadWriteExecute = 777, 33 | AllReadWrite = 666, 34 | Default = 644 35 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/FileStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 30.05.2024 - 11:05:42 5 | // Last edit: 30.05.2024 - 11:05:42 6 | 7 | #endregion 8 | 9 | using System.Diagnostics; 10 | using OpenSSH_GUI.Core.Enums; 11 | 12 | namespace OpenSSH_GUI.Core.Extensions; 13 | 14 | public static class FileStreamExtensions 15 | { 16 | public static async Task WithUnixPermissionsAsync(this FileStream stream, UnixPermissions permission) 17 | { 18 | if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX) 19 | { 20 | using var proc = new Process(); 21 | proc.StartInfo.FileName = "/bin/bash"; 22 | proc.StartInfo.ArgumentList.Add("-c"); 23 | proc.StartInfo.ArgumentList.Add($"chmod {(int)permission} {stream.Name}"); 24 | proc.Start(); 25 | await proc.WaitForExitAsync(); 26 | if (proc.ExitCode != 0) 27 | throw new InvalidOperationException($"Chmod execution failed with exit code : {proc.ExitCode}"); 28 | } 29 | 30 | return stream; 31 | } 32 | 33 | public static FileStream WithUnixPermissions(this FileStream stream, UnixPermissions permission) 34 | { 35 | return stream.WithUnixPermissionsAsync(permission).GetAwaiter().GetResult(); 36 | } 37 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/IConnectionCredentialsExtension.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | using Microsoft.EntityFrameworkCore; 10 | using OpenSSH_GUI.Core.Database.Context; 11 | using OpenSSH_GUI.Core.Database.DTO; 12 | using OpenSSH_GUI.Core.Interfaces.Credentials; 13 | 14 | namespace OpenSSH_GUI.Core.Extensions; 15 | 16 | /// 17 | /// Extension class for connection credentials. 18 | /// 19 | public static class ConnectionCredentialsExtensions 20 | { 21 | /// 22 | /// Saves the given connection credentials DTO in the database. 23 | /// 24 | /// The connection credentials. 25 | /// A task representing the asynchronous operation. The task result is the saved connection credentials DTO. 26 | public static async Task SaveDtoInDatabase(this IConnectionCredentials cc) 27 | { 28 | await using var dbContext = new OpenSshGuiDbContext(); 29 | 30 | var foundDuplicate = (cc is IMultiKeyConnectionCredentials or IPasswordConnectionCredentials 31 | ? await dbContext.ConnectionCredentialsDtos.FirstOrDefaultAsync(c => 32 | c.Hostname == cc.Hostname && 33 | c.Username == cc.Username && 34 | c.AuthType == cc.AuthType) 35 | : null) ?? (cc is IKeyConnectionCredentials dkcc 36 | ? await dbContext.ConnectionCredentialsDtos.FirstOrDefaultAsync(c => c.Hostname == cc.Hostname && 37 | c.Username == cc.Username && 38 | c.AuthType == cc.AuthType && 39 | c.KeyDtos.FirstOrDefault(e => e.AbsolutePath != dkcc.Key.AbsoluteFilePath) == null) 40 | : null); 41 | if (foundDuplicate is not null) return null; 42 | ConnectionCredentialsDto baseDto = new() 43 | { 44 | Id = cc.Id, 45 | Hostname = cc.Hostname, 46 | Username = cc.Username, 47 | AuthType = cc.AuthType, 48 | KeyDtos = [] 49 | }; 50 | var entity = dbContext.ConnectionCredentialsDtos.Add(baseDto); 51 | await dbContext.SaveChangesAsync(); 52 | baseDto = entity.Entity; 53 | 54 | switch (cc) 55 | { 56 | case IPasswordConnectionCredentials pcc: 57 | baseDto.Password = pcc.Password; 58 | baseDto.PasswordEncrypted = pcc.EncryptedPassword; 59 | break; 60 | case IKeyConnectionCredentials kcc: 61 | baseDto.KeyDtos.Add(dbContext.KeyDtos.First(e => e.AbsolutePath == kcc.Key.AbsoluteFilePath)); 62 | break; 63 | case IMultiKeyConnectionCredentials mcc: 64 | var dbKeys = dbContext.KeyDtos.Where(a => 65 | mcc.Keys.Select(b => b.AbsoluteFilePath).Contains(a.AbsolutePath)); 66 | baseDto.KeyDtos.AddRange(dbKeys); 67 | break; 68 | } 69 | 70 | await dbContext.SaveChangesAsync(); 71 | return await dbContext.ConnectionCredentialsDtos.FindAsync(baseDto.Id); 72 | } 73 | 74 | public static async Task RemoveDtoFromDatabase(this IConnectionCredentials cc) 75 | { 76 | await using var dbContext = new OpenSshGuiDbContext(); 77 | await dbContext.ConnectionCredentialsDtos.Where(e => e.Id == cc.Id).ExecuteDeleteAsync(); 78 | await dbContext.SaveChangesAsync(); 79 | } 80 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/IPrivateKeySourceExtensions.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 16.05.2024 - 08:05:28 3 | // Last edit: 16.05.2024 - 08:05:28 4 | 5 | using Renci.SshNet; 6 | using SshNet.Keygen.Extensions; 7 | 8 | namespace OpenSSH_GUI.Core.Extensions; 9 | 10 | /// 11 | /// Provides extension methods for the IPrivateKeySource interface. 12 | /// 13 | public static class PrivateKeySourceExtensions 14 | { 15 | /// 16 | /// Retrieves the fingerprint hash of the private key source. 17 | /// 18 | /// The private key source. 19 | /// The fingerprint hash of the private key source. 20 | public static string FingerprintHash(this IPrivateKeySource privateKeySource) 21 | { 22 | return privateKeySource 23 | .Fingerprint() 24 | .Split(' ') 25 | .First(e => e.StartsWith("SHA")) 26 | .Split(':') 27 | .Last(); 28 | } 29 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/KeyTypeExtension.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:24 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using OpenSSH_GUI.Core.Interfaces.Keys; 11 | using OpenSSH_GUI.Core.Lib.Keys; 12 | 13 | namespace OpenSSH_GUI.Core.Extensions; 14 | 15 | /// 16 | /// Provides extension methods for working with key types. 17 | /// 18 | public static class KeyTypeExtension 19 | { 20 | /// 21 | /// Retrieves available SSH key types. 22 | /// 23 | /// The collection of available SSH key types. 24 | public static IEnumerable GetAvailableKeyTypes() 25 | { 26 | return Enum.GetValues().Select(keyType => new SshKeyType(keyType)).ToList(); 27 | } 28 | 29 | 30 | /// 31 | /// Retrieves the possible bit values for a given key type. 32 | /// 33 | /// The key type. 34 | /// An enumerable of possible bit values. 35 | public static IEnumerable GetBitValues(this KeyType type) 36 | { 37 | return type switch 38 | { 39 | KeyType.RSA => [1024, 2048, 3072, 4096], 40 | KeyType.ECDSA => [256, 384, 521], 41 | _ => [] 42 | }; 43 | } 44 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/SshConfigFilesExtension.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:25 6 | 7 | #endregion 8 | 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Logging.Abstractions; 11 | using OpenSSH_GUI.Core.Enums; 12 | 13 | namespace OpenSSH_GUI.Core.Extensions; 14 | 15 | /// 16 | /// Provides extension methods for handling SSH configuration files. 17 | /// 18 | public static class SshConfigFilesExtension 19 | { 20 | /// 21 | /// Represents the path for SSH with variable on Linux. 22 | /// 23 | private const string SshPathWithVariableLinux = "%HOME%/.ssh"; 24 | 25 | /// 26 | /// Represents the SSH path with a variable for Windows operating system. 27 | /// 28 | private const string SshPathWithVariableWindows = "%USERPROFILE%\\.ssh"; 29 | 30 | /// 31 | /// Represents the root SSH path for Linux. 32 | /// 33 | private const string SshRootPathLinux = "/etc/ssh"; 34 | 35 | /// 36 | /// The SSH root path on Windows. 37 | /// 38 | private const string SshRootPathWin = "%PROGRAMDATA%\\ssh"; 39 | 40 | public static void ValidateDirectories(ILogger? logger = null) 41 | { 42 | logger ??= NullLogger.Instance; 43 | try 44 | { 45 | if (!Directory.Exists(GetRootSshPath())) Directory.CreateDirectory(GetRootSshPath()); 46 | if (!Directory.Exists(GetBaseSshPath())) Directory.CreateDirectory(GetBaseSshPath()); 47 | } 48 | catch (Exception e) 49 | { 50 | logger.LogError(e, "Error creating config path"); 51 | } 52 | } 53 | 54 | /// 55 | /// Retrieves the root SSH path based on the platform. 56 | /// 57 | /// Indicates whether to resolve environment variables in the path. Defaults to true. 58 | /// The platform ID. Defaults to the current operating system platform. 59 | /// The root SSH path. 60 | public static string GetRootSshPath(bool resolve = true, PlatformID? platformId = null) 61 | { 62 | var path = (platformId ?? Environment.OSVersion.Platform) switch 63 | { 64 | PlatformID.Win32S or PlatformID.Win32Windows or PlatformID.Win32NT or PlatformID.WinCE => SshRootPathWin, 65 | PlatformID.Unix or PlatformID.MacOSX => SshRootPathLinux, 66 | _ => throw new NotSupportedException( 67 | $"Platform {Environment.OSVersion.Platform.ToString().ToLower()} is not supported!") 68 | }; 69 | return resolve ? Environment.ExpandEnvironmentVariables(path) : path; 70 | } 71 | 72 | /// Gets the base path to the SSH directory. 73 | /// @param resolve Boolean value indicating whether to resolve environment variables in the path. Default is true. 74 | /// @param platformId (optional) The platform ID. Default is the platform of the current environment. 75 | /// @returns The base path to the SSH directory. 76 | /// / 77 | public static string GetBaseSshPath(bool resolve = true, PlatformID? platformId = null) 78 | { 79 | var path = (platformId ?? Environment.OSVersion.Platform) switch 80 | { 81 | PlatformID.Win32S or PlatformID.Win32Windows or PlatformID.Win32NT or PlatformID.WinCE => 82 | SshPathWithVariableWindows, 83 | PlatformID.Unix or PlatformID.MacOSX => SshPathWithVariableLinux, 84 | _ => throw new NotSupportedException( 85 | $"Platform {Environment.OSVersion.Platform.ToString().ToLower()} is not supported!") 86 | }; 87 | return resolve ? Environment.ExpandEnvironmentVariables(path) : path; 88 | } 89 | 90 | /// 91 | /// Retrieves the file path corresponding to the specified enum value. 92 | /// 93 | /// The enum value representing the SSH config file. 94 | /// 95 | /// A boolean value indicating whether to resolve the file path using 96 | /// or . 97 | /// 98 | /// The target platform identifier to determine the file path format. 99 | /// The file path as a . 100 | public static string GetPathOfFile(this SshConfigFiles files, bool resolve = true, PlatformID? platform = null) 101 | { 102 | var path = Path.Combine(files switch 103 | { 104 | SshConfigFiles.Authorized_Keys or 105 | SshConfigFiles.Known_Hosts or 106 | SshConfigFiles.Config => GetBaseSshPath(resolve, platform), 107 | SshConfigFiles.Sshd_Config => GetRootSshPath(resolve, platform), 108 | _ => throw new ArgumentException("Invalid value for \"files\"") 109 | }, Enum.GetName(files)!.ToLower()); 110 | platform ??= Environment.OSVersion.Platform; 111 | path = platform is PlatformID.Unix ? path.Replace('\\', '/') : path.Replace('/', '\\'); 112 | return path; 113 | } 114 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/SshKeyFormatExtension.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 18.05.2024 - 14:05:52 3 | // Last edit: 18.05.2024 - 14:05:52 4 | 5 | using SshNet.Keygen; 6 | 7 | namespace OpenSSH_GUI.Core.Extensions; 8 | 9 | /// 10 | /// Provides extension methods for working with SSH key formats. 11 | /// 12 | public static class SshKeyFormatExtension 13 | { 14 | /// 15 | /// Represents the file extension for OpenSSH Public Key format. 16 | /// 17 | private const string OpenSshPublicKeyExtension = ".pub"; 18 | 19 | /// 20 | /// Represents the file extension used for PuTTY private key files. 21 | /// 22 | private const string PuttyKeyExtension = ".ppk"; 23 | 24 | /// 25 | /// Returns the file extension associated with the specified SSH key format. 26 | /// 27 | /// The SSH key format. 28 | /// True if the extension is for a public key; otherwise, false. Defaults to true. 29 | /// 30 | /// The file extension associated with the specified SSH key format. Returns null if the format is not supported 31 | /// or the extension is not applicable. 32 | /// 33 | public static string? GetExtension(this SshKeyFormat format, bool @public = true) 34 | { 35 | return format switch 36 | { 37 | SshKeyFormat.OpenSSH when @public => OpenSshPublicKeyExtension, 38 | SshKeyFormat.OpenSSH => null, 39 | SshKeyFormat.PuTTYv2 or SshKeyFormat.PuTTYv3 => PuttyKeyExtension, 40 | _ => null 41 | }; 42 | } 43 | 44 | /// 45 | /// Changes the file extension of the given path to match the specified SSH key format. 46 | /// 47 | /// The SSH key format. 48 | /// The path to the file. 49 | /// Indicates whether the key is public. Default is true. 50 | /// The modified file path with the updated extension. 51 | public static string ChangeExtension(this SshKeyFormat format, string path, bool @public = true) 52 | { 53 | return Path.ChangeExtension(path, format.GetExtension(@public)); 54 | } 55 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Extensions/SshKeyGenerateParamsExtensions.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 18.05.2024 - 13:05:14 3 | // Last edit: 18.05.2024 - 13:05:14 4 | 5 | using OpenSSH_GUI.Core.Enums; 6 | using OpenSSH_GUI.Core.Lib.Misc; 7 | using SshNet.Keygen; 8 | using SshNet.Keygen.SshKeyEncryption; 9 | 10 | namespace OpenSSH_GUI.Core.Extensions; 11 | 12 | /// 13 | /// Extensions for the class. 14 | /// 15 | public static class SshKeyGenerateParamsExtensions 16 | { 17 | /// 18 | /// Converts the given object to an object. 19 | /// 20 | /// The object to convert. 21 | /// An object. 22 | public static SshKeyGenerateInfo ToInfo(this SshKeyGenerateParams @params) 23 | { 24 | return new SshKeyGenerateInfo 25 | { 26 | KeyType = @params.KeyType switch 27 | { 28 | KeyType.RSA => SshKeyType.RSA, 29 | KeyType.ECDSA => SshKeyType.ECDSA, 30 | KeyType.ED25519 => SshKeyType.ED25519, 31 | _ => throw new ArgumentOutOfRangeException() 32 | }, 33 | Comment = @params.Comment, 34 | KeyFormat = @params.KeyFormat, 35 | KeyLength = @params.KeyLength, 36 | Encryption = !string.IsNullOrWhiteSpace(@params.Password) 37 | ? new SshKeyEncryptionAes256(@params.Password, 38 | @params.KeyFormat is not SshKeyFormat.OpenSSH ? new PuttyV3Encryption() : null) 39 | : new SshKeyEncryptionNone() 40 | }; 41 | } 42 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/AuthorizedKeys/IAuthorizedKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | 11 | namespace OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 12 | 13 | /// 14 | /// Represents an authorized key. 15 | /// 16 | public interface IAuthorizedKey 17 | { 18 | /// 19 | /// Enumeration for SSH key types. 20 | /// 21 | KeyType KeyType { get; } 22 | 23 | /// 24 | /// Represents an authorized key entry in an authorized keys file. 25 | /// 26 | string Fingerprint { get; } 27 | 28 | /// 29 | /// Represents an authorized key entry in an authorized keys file. 30 | /// 31 | string Comment { get; } 32 | 33 | /// *Property: MarkedForDeletion** 34 | bool MarkedForDeletion { get; set; } 35 | 36 | /// 37 | /// Returns the full key entry of an authorized key. 38 | /// 39 | /// The full key entry string. 40 | string GetFullKeyEntry { get; } 41 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/AuthorizedKeys/IAuthorizedKeysFile.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | using System.Collections.ObjectModel; 10 | using OpenSSH_GUI.Core.Interfaces.Keys; 11 | using ReactiveUI; 12 | 13 | namespace OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 14 | 15 | /// 16 | /// Interface for an Authorized Keys file. 17 | /// 18 | public interface IAuthorizedKeysFile : IReactiveObject 19 | { 20 | /// 21 | /// Represents the authorized keys file. 22 | /// 23 | ObservableCollection AuthorizedKeys { get; set; } 24 | 25 | /// 26 | /// Adds an authorized key to the authorized keys file. 27 | /// 28 | /// The SSH key to be added. 29 | /// True if the key was successfully added, otherwise false. 30 | bool AddAuthorizedKey(ISshKey key); 31 | 32 | /// 33 | /// Applies the changes to the authorized keys file. 34 | /// 35 | /// The collection of keys to be applied as changes. 36 | /// True if any changes were made to the authorized keys file; otherwise, false. 37 | bool ApplyChanges(IEnumerable keys); 38 | 39 | /// 40 | /// Persists the changes made to the authorized keys file. 41 | /// 42 | /// The modified object. 43 | IAuthorizedKeysFile PersistChangesInFile(); 44 | 45 | /// 46 | /// Adds an authorized key asynchronously. 47 | /// 48 | /// The SSH key to be added. 49 | /// 50 | /// A task representing the asynchronous operation. The task result is a boolean value indicating whether the key 51 | /// was added successfully. 52 | /// 53 | Task AddAuthorizedKeyAsync(ISshKey key); 54 | 55 | /// 56 | /// Removes the specified SSH key from the authorized keys list. 57 | /// 58 | /// The SSH key to remove. 59 | /// 60 | /// Returns true if the key is successfully removed; 61 | /// otherwise, false. 62 | /// 63 | bool RemoveAuthorizedKey(ISshKey key); 64 | 65 | /// 66 | /// Exports the content of the authorized keys file. 67 | /// 68 | /// Indicates whether to export for the local machine or remote server. Default is true (local). 69 | /// 70 | /// The platform ID of the server. If null, the current OS platform will be used. Only applicable if 71 | /// 'local' is set to false. 72 | /// 73 | /// The content of the authorized keys file as a string. 74 | string ExportFileContent(bool local = true, PlatformID? platform = null); 75 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Credentials/IConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:34 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Enums; 11 | using Renci.SshNet; 12 | 13 | namespace OpenSSH_GUI.Core.Interfaces.Credentials; 14 | 15 | /// 16 | /// Represents the interface for connection credentials. 17 | /// 18 | public interface IConnectionCredentials 19 | { 20 | /// 21 | /// Represents the unique identifier for a connection credential. 22 | /// 23 | /// 24 | /// The ID is used to uniquely identify a connection credential in the system. 25 | /// 26 | int Id { get; set; } 27 | 28 | /// 29 | /// Represents the host name for a connection. 30 | /// 31 | /// 32 | /// The host name is an essential property for establishing a connection to a remote server. 33 | /// It identifies the target server that the client wants to connect to. 34 | /// 35 | string Hostname { get; set; } 36 | 37 | /// 38 | /// Represents the base class for connection credentials. 39 | /// 40 | int Port { get; } 41 | 42 | /// 43 | /// Represents the username used for the connection credentials. 44 | /// 45 | string Username { get; set; } 46 | 47 | /// 48 | /// Gets the display string representation of the connection credentials. 49 | /// 50 | /// The display string in the format "{Username}@{Hostname}:{Port}" 51 | [JsonIgnore] 52 | string Display { get; } 53 | 54 | /// 55 | /// Gets the authentication type for the connection credentials. 56 | /// 57 | /// 58 | /// The property represents the type of authentication to be used for the connection 59 | /// credentials. 60 | /// The available authentication types are: 61 | /// - Password: The connection credentials use password authentication. 62 | /// - Key: The connection credentials use key authentication. 63 | /// - MultiKey: The connection credentials use multi-key authentication. 64 | /// 65 | AuthType AuthType { get; } 66 | 67 | /// 68 | /// Retrieves the connection information based on the provided credentials. 69 | /// 70 | /// The object representing the SSH connection information. 71 | ConnectionInfo GetConnectionInfo(); 72 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Credentials/IKeyConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:34 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Interfaces.Keys; 11 | 12 | namespace OpenSSH_GUI.Core.Interfaces.Credentials; 13 | 14 | /// 15 | /// Represents connection credentials for SSH using key-based authentication. 16 | /// 17 | public interface IKeyConnectionCredentials : IConnectionCredentials 18 | { 19 | /// 20 | /// Represents a connection credential that includes an SSH key. 21 | /// 22 | [JsonIgnore] 23 | ISshKey? Key { get; set; } 24 | 25 | /// 26 | /// Renews the SSH key used for authentication. 27 | /// 28 | /// The password for the key file (optional). 29 | void RenewKey(string? password = null); 30 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Credentials/IMultiKeyConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Interfaces.Keys; 11 | 12 | namespace OpenSSH_GUI.Core.Interfaces.Credentials; 13 | 14 | /// 15 | /// Represents the interface for multi-key connection credentials. 16 | /// 17 | public interface IMultiKeyConnectionCredentials : IConnectionCredentials 18 | { 19 | /// 20 | /// Represents the credentials for a multi-key connection. 21 | /// 22 | [JsonIgnore] 23 | IEnumerable? Keys { get; set; } 24 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Credentials/IPasswordConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Interfaces.Credentials; 10 | 11 | /// 12 | /// Represents the interface for password-based connection credentials. 13 | /// 14 | public interface IPasswordConnectionCredentials : IConnectionCredentials 15 | { 16 | /// 17 | /// Represents the password connection credentials. 18 | /// 19 | string Password { get; set; } 20 | 21 | /// 22 | /// Gets or sets a value indicating whether the password is encrypted. 23 | /// 24 | bool EncryptedPassword { get; set; } 25 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Keys/IPpkKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:32 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | 11 | namespace OpenSSH_GUI.Core.Interfaces.Keys; 12 | 13 | /// 14 | /// Represents an interface for a PpkKey. 15 | /// 16 | public interface IPpkKey : ISshKey 17 | { 18 | /// 19 | /// Represents the encryption type of an SSH key. 20 | /// 21 | EncryptionType EncryptionType { get; } 22 | 23 | /// 24 | /// Gets the public key string of the SSH key. 25 | /// 26 | /// 27 | /// This property represents the public key string of the SSH key. 28 | /// 29 | string PublicKeyString { get; } 30 | 31 | /// 32 | /// Represents the private key string of an SSH key. 33 | /// 34 | /// 35 | /// The private key string. 36 | /// 37 | string PrivateKeyString { get; } 38 | 39 | /// 40 | /// Represents a PPK key. 41 | /// 42 | string PrivateMAC { get; } 43 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Keys/ISshKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:33 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Interfaces.Misc; 10 | 11 | namespace OpenSSH_GUI.Core.Interfaces.Keys; 12 | 13 | /// 14 | /// Represents an SSH key. 15 | /// 16 | public interface ISshKey : IKeyBase 17 | { 18 | /// 19 | /// Represents the unique identifier for an SSH key. 20 | /// 21 | int Id { get; set; } 22 | 23 | /// 24 | /// Represents a comment associated with an SSH key. 25 | /// 26 | string Comment { get; } 27 | 28 | /// 29 | /// Gets a value indicating whether the SSH key is a Putty key. 30 | /// 31 | /// 32 | /// The SSH key is considered a Putty key if its format is not OpenSSH. 33 | /// 34 | bool IsPuttyKey { get; } 35 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Keys/ISshKeyType.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:33 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | 11 | namespace OpenSSH_GUI.Core.Interfaces.Keys; 12 | 13 | /// 14 | /// Represents an SSH key type. 15 | /// 16 | public interface ISshKeyType 17 | { 18 | /// 19 | /// Interface for SSH key types. 20 | /// 21 | KeyType BaseType { get; } 22 | 23 | /// 24 | /// Represents an SSH key type. 25 | /// 26 | string KeyTypeText { get; } 27 | 28 | /// 29 | /// Specifies whether the SSH key type has a default bit size. 30 | /// 31 | bool HasDefaultBitSize { get; } 32 | 33 | /// 34 | /// Gets or sets the current bit size of the SSH key type. 35 | /// 36 | int CurrentBitSize { get; set; } 37 | 38 | /// 39 | /// Represents a property that provides possible bit sizes for SSH key types. 40 | /// 41 | IEnumerable PossibleBitSizes { get; } 42 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Keys/ISshPrivateKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:33 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Interfaces.Keys; 10 | 11 | /// 12 | /// Represents an SSH private key. 13 | /// 14 | public interface ISshPrivateKey : ISshKey; -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Keys/ISshPublicKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:33 6 | 7 | #endregion 8 | 9 | namespace OpenSSH_GUI.Core.Interfaces.Keys; 10 | 11 | /// 12 | /// Represents a SSH public key. 13 | /// 14 | public interface ISshPublicKey : ISshKey 15 | { 16 | /// 17 | /// Represents the private key associated with an SSH public key. 18 | /// 19 | ISshKey PrivateKey { get; } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/KnownHosts/IKnownHost.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:34 6 | 7 | #endregion 8 | 9 | using ReactiveUI; 10 | 11 | namespace OpenSSH_GUI.Core.Interfaces.KnownHosts; 12 | 13 | /// 14 | /// Represents a known host in the OpenSSH GUI. 15 | /// 16 | public interface IKnownHost : IReactiveObject 17 | { 18 | /// 19 | /// Represents a known host. 20 | /// 21 | string Host { get; } 22 | 23 | /// 24 | /// Represents a known host in the OpenSSH GUI. 25 | /// 26 | bool DeleteWholeHost { get; } 27 | 28 | /// 29 | /// Represents a known host in the OpenSSH GUI. 30 | /// 31 | List Keys { get; set; } 32 | 33 | /// 34 | /// Toggles the marked for deletion flag of each within the list. 35 | /// If the property is true, it sets the flag to false for all keys. Otherwise, it sets 36 | /// the flag to true for all keys. 37 | /// 38 | void KeysDeletionSwitch(); 39 | 40 | /// 41 | /// Retrieves all entries for a known host in the known hosts file. 42 | /// 43 | /// 44 | /// Returns a string containing all the entries for the known host. 45 | /// If the entire host is marked for deletion, returns the line ending character. 46 | /// 47 | string GetAllEntries(); 48 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/KnownHosts/IKnownHostKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:34 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using ReactiveUI; 11 | 12 | namespace OpenSSH_GUI.Core.Interfaces.KnownHosts; 13 | 14 | /// Represents a known host key in the OpenSSH GUI. 15 | /// / 16 | public interface IKnownHostKey : IReactiveObject 17 | { 18 | /// 19 | /// Represents the type of a known host key. 20 | /// 21 | KeyType KeyType { get; } 22 | 23 | /// 24 | /// Represents a known host key in the OpenSSH GUI. 25 | /// 26 | string Fingerprint { get; } 27 | 28 | /// 29 | /// Represents a known host key in the OpenSSH GUI. 30 | /// 31 | string EntryWithoutHost { get; } 32 | 33 | /// 34 | /// Gets or sets whether the KnownHostKey is marked for deletion. 35 | /// 36 | bool MarkedForDeletion { get; set; } 37 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/KnownHosts/IKnownHostsFile.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:34 6 | 7 | #endregion 8 | 9 | using System.Collections.ObjectModel; 10 | using ReactiveUI; 11 | 12 | namespace OpenSSH_GUI.Core.Interfaces.KnownHosts; 13 | 14 | /// 15 | /// Represents a known hosts file. 16 | /// 17 | public interface IKnownHostsFile : IReactiveObject 18 | { 19 | /// 20 | /// Represents the line ending character used in the known_hosts file. 21 | /// 22 | static string LineEnding { get; set; } 23 | 24 | /// 25 | /// Represents a file that contains known host entries. 26 | /// 27 | ObservableCollection KnownHosts { get; } 28 | 29 | /// 30 | /// Asynchronously reads the contents of the known hosts file. 31 | /// 32 | /// 33 | /// The file stream to read from. If null, the method reads from the file specified in the 34 | /// constructor. 35 | /// 36 | /// A task representing the asynchronous operation. 37 | Task ReadContentAsync(FileStream? stream = null); 38 | 39 | /// 40 | /// Synchronizes the known hosts with the given list of new known hosts. 41 | /// 42 | /// The new known hosts to synchronize. 43 | void SyncKnownHosts(IEnumerable newKnownHosts); 44 | 45 | /// 46 | /// Updates the content of the known hosts file. 47 | /// 48 | /// A task representing the update operation. 49 | Task UpdateFile(); 50 | 51 | /// 52 | /// Retrieves the updated contents of the known hosts file. 53 | /// 54 | /// The platform ID of the server. 55 | /// The updated contents of the known hosts file as a string. 56 | /// 57 | /// This method retrieves the updated contents of the known hosts file. 58 | /// It takes the platform ID of the server as a parameter and returns the 59 | /// updated contents of the known hosts file as a string. The method 60 | /// checks if the instance of the KnownHostsFile class is created from 61 | /// the server or not. If it is not created from the server, it returns 62 | /// an empty string. It sets the LineEnding property based on the platform 63 | /// ID provided. It then aggregates the known hosts entries excluding those 64 | /// which are flagged for deletion and returns the updated contents as a string. 65 | /// 66 | /// The platform ID of the server. 67 | string GetUpdatedContents(PlatformID platformId); 68 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Misc/IKeyBase.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 01:05:51 5 | // Last edit: 15.05.2024 - 01:05:48 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Database.DTO; 10 | using OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 11 | using OpenSSH_GUI.Core.Interfaces.Keys; 12 | using Renci.SshNet; 13 | using SshNet.Keygen; 14 | 15 | namespace OpenSSH_GUI.Core.Interfaces.Misc; 16 | 17 | /// 18 | /// Represents the base interface for SSH keys. 19 | /// 20 | public interface IKeyBase 21 | { 22 | /// 23 | /// Gets a value indicating whether the SSH key has a password. 24 | /// 25 | bool HasPassword { get; } 26 | 27 | /// 28 | /// Gets a value indicating whether the key requires a password. 29 | /// 30 | /// 31 | /// This property is calculated based on the and properties. 32 | /// If the key has a password and the password has not been successfully provided, then this property will be true. 33 | /// 34 | bool NeedPassword { get; } 35 | 36 | /// 37 | /// Gets or sets a value indicating whether the password authentication was successful for the SSH key. 38 | /// 39 | bool PasswordSuccess { get; set; } 40 | 41 | /// 42 | /// Represents a password associated with a key. 43 | /// 44 | string? Password { get; set; } 45 | 46 | /// 47 | /// Gets the absolute file path of the SSH key. 48 | /// 49 | /// The absolute file path of the SSH key. 50 | string AbsoluteFilePath { get; } 51 | 52 | /// 53 | /// Represents a fingerprint of a key. 54 | /// 55 | string Fingerprint { get; } 56 | 57 | /// 58 | /// Gets the filename of the key. 59 | /// 60 | string Filename { get; } 61 | 62 | /// Gets the format of the SSH key. 63 | /// @return The format of the SSH key. 64 | /// / 65 | SshKeyFormat Format { get; } 66 | 67 | /// 68 | /// Represents the key type for an SSH key. 69 | /// 70 | ISshKeyType KeyType { get; } 71 | 72 | /// 73 | /// Export the public key in OpenSSH format. 74 | /// 75 | /// The public key in OpenSSH format. 76 | string? ExportOpenSshPublicKey(); 77 | 78 | /// 79 | /// Exports the private key in OpenSSH format. 80 | /// 81 | /// The private key in OpenSSH format as a string, or null if the key source is null. 82 | string? ExportOpenSshPrivateKey(); 83 | 84 | /// 85 | /// Exports the public key in PuTTY format. 86 | /// 87 | /// The public key in PuTTY format. 88 | string? ExportPuttyPublicKey(); 89 | 90 | /// 91 | /// Exports the Putty PPK key format of the SSH key. 92 | /// 93 | /// 94 | /// The Putty PPK key format as a string. 95 | /// 96 | string? ExportPuttyPpkKey(); 97 | 98 | /// 99 | /// Exports the text representation of the key. 100 | /// 101 | /// The text representation of the key. 102 | string? ExportTextOfKey(); 103 | 104 | /// 105 | /// Exports the authorized key entry for the key. 106 | /// 107 | /// The authorized key entry for the key. 108 | string? ExportAuthorizedKeyEntry(); 109 | 110 | /// 111 | /// Exports the authorized key as an instance of IAuthorizedKey. 112 | /// 113 | /// The exported authorized key. 114 | IAuthorizedKey ExportAuthorizedKey(); 115 | 116 | /// 117 | /// Retrieves the SSH.NET key type of the key. 118 | /// 119 | /// The SSH.NET key type of the key. 120 | IPrivateKeySource? GetSshNetKeyType(); 121 | 122 | /// 123 | /// Deletes the SSH key. 124 | /// 125 | void DeleteKey(); 126 | 127 | /// 128 | /// Converts the current instance of the class to an instance of . 129 | /// 130 | /// An instance of . 131 | SshKeyDto ToDto(); 132 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Interfaces/Misc/IServerConnection.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:33 6 | 7 | #endregion 8 | 9 | using System.Diagnostics.CodeAnalysis; 10 | using OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 11 | using OpenSSH_GUI.Core.Interfaces.Credentials; 12 | using OpenSSH_GUI.Core.Interfaces.KnownHosts; 13 | using ReactiveUI; 14 | 15 | namespace OpenSSH_GUI.Core.Interfaces.Misc; 16 | 17 | /// Represents a server connection. 18 | /// / 19 | public interface IServerConnection : IReactiveObject, IDisposable 20 | { 21 | /// 22 | /// Represents the credentials for a server connection. 23 | /// 24 | IConnectionCredentials ConnectionCredentials { get; } 25 | 26 | /// 27 | /// Gets or sets the connection time of the server. 28 | /// 29 | DateTime ConnectionTime { get; set; } 30 | 31 | /// 32 | /// Gets or sets a value indicating whether the server connection is currently open. 33 | /// 34 | bool IsConnected { get; set; } 35 | 36 | /// 37 | /// Represents a server connection. 38 | /// 39 | string ConnectionString { get; } 40 | 41 | /// 42 | /// Represents a server connection. 43 | /// 44 | PlatformID ServerOs { get; set; } 45 | 46 | /// 47 | /// Tests the connection to the server and opens a connection if successful. 48 | /// 49 | /// 50 | /// An that represents the error that occurred during the test and open 51 | /// connection process, if any. If the test and open connection process is successful, this value is null. 52 | /// 53 | /// true if the test and open connection process is successful; otherwise, false. 54 | bool TestAndOpenConnection([NotNullWhen(false)] out Exception? exception); 55 | 56 | /// 57 | /// Closes the server connection. 58 | /// 59 | /// An exception that occurred during the closing of the connection, if any. 60 | /// Returns true if the connection was closed successfully, otherwise false. 61 | bool CloseConnection([NotNullWhen(false)] out Exception? ex); 62 | 63 | /// 64 | /// Retrieves the known hosts file from the server. 65 | /// 66 | /// The known hosts file as an instance of IKnownHostsFile. 67 | IKnownHostsFile GetKnownHostsFromServer(); 68 | 69 | /// 70 | /// Writes the known hosts file to the server. 71 | /// 72 | /// The known hosts file to write. 73 | /// Returns true if the known hosts file was successfully written to the server, false otherwise. 74 | bool WriteKnownHostsToServer(IKnownHostsFile knownHostsFile); 75 | 76 | /// 77 | /// Retrieves the authorized keys file from the server. 78 | /// 79 | /// The authorized keys file from the server. 80 | IAuthorizedKeysFile GetAuthorizedKeysFromServer(); 81 | 82 | /// 83 | /// Writes the changes made to the authorized keys file to the server. 84 | /// 85 | /// The authorized keys file containing the changes. 86 | /// True if the changes were successfully written to the server; otherwise, false. 87 | bool WriteAuthorizedKeysChangesToServer(IAuthorizedKeysFile authorizedKeysFile); 88 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/AuthorizedKeys/AuthorizedKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:32 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 11 | 12 | namespace OpenSSH_GUI.Core.Lib.AuthorizedKeys; 13 | 14 | /// 15 | /// Represents an authorized key entry in an authorized keys file. 16 | /// 17 | public class AuthorizedKey : IAuthorizedKey 18 | { 19 | /// 20 | /// Represents an authorized key entry in the authorized_keys file. 21 | /// 22 | public AuthorizedKey(string keyEntry) 23 | { 24 | var split = keyEntry.Split(' '); 25 | if (split.Length != 3) 26 | throw new IndexOutOfRangeException("Authorized Keys must contain TYPE FINGERPRINT COMMENT"); 27 | 28 | KeyTypeDeclarationInFile = split[0]; 29 | KeyType = Enum.Parse( 30 | KeyTypeDeclarationInFile.StartsWith("ssh-") 31 | ? KeyTypeDeclarationInFile.Replace("ssh-", "") 32 | : KeyTypeDeclarationInFile.Split('-')[0], true); 33 | Fingerprint = split[1]; 34 | Comment = split[2]; 35 | } 36 | 37 | /// 38 | /// Represents the key type declaration in the authorized key file. 39 | /// 40 | /// 41 | /// This property holds the key type declaration as it appears in the authorized key file. 42 | /// The key type declaration indicates the algorithm used for the key. 43 | /// 44 | private string KeyTypeDeclarationInFile { get; } 45 | 46 | /// 47 | /// Represents the type of an authorized key. 48 | /// 49 | public KeyType KeyType { get; } 50 | 51 | /// 52 | /// Represents an authorized key entry. 53 | /// 54 | public string Fingerprint { get; } 55 | 56 | /// 57 | /// Represents an authorized key entry. 58 | /// 59 | public string Comment { get; } 60 | 61 | /// 62 | /// Represents an authorized key. 63 | /// 64 | public bool MarkedForDeletion { get; set; } 65 | 66 | /// 67 | /// Gets the full key entry string of an authorized key. 68 | /// 69 | /// 70 | /// The full key entry string consists of the key type, fingerprint, and comment separated by spaces. 71 | /// 72 | /// The full key entry string. 73 | public string GetFullKeyEntry => $"{KeyTypeDeclarationInFile} {Fingerprint} {Comment}"; 74 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Credentials/ConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:31 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Enums; 11 | using OpenSSH_GUI.Core.Interfaces.Credentials; 12 | using Renci.SshNet; 13 | 14 | namespace OpenSSH_GUI.Core.Lib.Credentials; 15 | 16 | /// 17 | /// Represents the base class for connection credentials. 18 | /// 19 | public class ConnectionCredentials(string hostname, string username, AuthType authType) 20 | : IConnectionCredentials 21 | { 22 | /// 23 | /// Represents the unique identifier for a connection credentials object. 24 | /// This property is used in classes related to connection credentials and server settings. 25 | /// 26 | public int Id { get; set; } 27 | 28 | /// 29 | /// Represents the hostname of a server. 30 | /// This property is used in classes related to connection credentials and server settings. 31 | /// 32 | public string Hostname { get; set; } = hostname; 33 | 34 | /// 35 | /// Represents the port number used for establishing an SSH connection. 36 | /// 37 | public int Port => Hostname.Contains(':') ? int.Parse(Hostname.Split(':')[1]) : 22; 38 | 39 | /// 40 | /// Represents the username property of a connection credentials. 41 | /// 42 | public string Username { get; set; } = username; 43 | 44 | /// 45 | /// Retrieves the connection information based on the provided credentials. 46 | /// 47 | /// 48 | /// The object representing the SSH connection information. 49 | /// 50 | public virtual ConnectionInfo GetConnectionInfo() 51 | { 52 | return new ConnectionInfo(Hostname, Username); 53 | } 54 | 55 | /// 56 | /// Gets the display string for the connection credentials. 57 | /// 58 | [JsonIgnore] 59 | public string Display => ToString(); 60 | 61 | /// 62 | /// Represents the authentication type for a connection. 63 | /// 64 | public AuthType AuthType { get; set; } = authType; 65 | 66 | /// 67 | /// Returns a string representation of the ConnectionCredentials object. 68 | /// 69 | /// A string in the format "{Username}@{Hostname}:{Port}" 70 | public override string ToString() 71 | { 72 | return $"{Username}@{Hostname}{(Port is 22 ? "" : $":{Port}")}"; 73 | } 74 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Credentials/KeyConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:30 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Enums; 11 | using OpenSSH_GUI.Core.Interfaces.Credentials; 12 | using OpenSSH_GUI.Core.Interfaces.Keys; 13 | using OpenSSH_GUI.Core.Lib.Static; 14 | using Renci.SshNet; 15 | 16 | namespace OpenSSH_GUI.Core.Lib.Credentials; 17 | 18 | /// 19 | /// Represents the credentials for a key-based connection to a server. 20 | /// 21 | public class KeyConnectionCredentials : ConnectionCredentials, IKeyConnectionCredentials 22 | { 23 | /// 24 | /// Represents connection credentials using SSH key authentication. 25 | /// 26 | public KeyConnectionCredentials(string hostname, string username, ISshKey? key) : base(hostname, username, 27 | AuthType.Key) 28 | { 29 | Key = key; 30 | } 31 | 32 | /// 33 | /// Represents connection credentials that include an SSH key for authentication. 34 | /// 35 | [JsonIgnore] 36 | public ISshKey? Key { get; set; } 37 | 38 | 39 | /// 40 | /// Renews the SSH key used for authentication. 41 | /// 42 | /// The password for the key file (optional). 43 | public void RenewKey(string? password = null) 44 | { 45 | Key = KeyFactory.FromDtoId(Key.Id); 46 | } 47 | 48 | /// 49 | /// Retrieves the connection information based on the provided credentials. 50 | /// 51 | /// 52 | /// The object representing the SSH connection information. 53 | /// 54 | public override ConnectionInfo GetConnectionInfo() 55 | { 56 | return new PrivateKeyConnectionInfo(Hostname, Username, ProxyTypes.None, "", 0, Key.GetSshNetKeyType()); 57 | } 58 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Credentials/MultiKeyConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:31 6 | 7 | #endregion 8 | 9 | using System.Text.Json.Serialization; 10 | using OpenSSH_GUI.Core.Enums; 11 | using OpenSSH_GUI.Core.Interfaces.Credentials; 12 | using OpenSSH_GUI.Core.Interfaces.Keys; 13 | using Renci.SshNet; 14 | 15 | namespace OpenSSH_GUI.Core.Lib.Credentials; 16 | 17 | /// *MultiKeyConnectionCredentials(string hostname, string username, 18 | /// 19 | /// ? keys)** 20 | public class MultiKeyConnectionCredentials : ConnectionCredentials, IMultiKeyConnectionCredentials 21 | { 22 | /// 23 | /// Represents a set of connection credentials for a multi-key authentication. 24 | /// 25 | public MultiKeyConnectionCredentials(string hostname, string username, IEnumerable? keys) : base(hostname, 26 | username, AuthType.MultiKey) 27 | { 28 | Keys = keys; 29 | } 30 | 31 | /// 32 | /// Represents the credentials for a multi-key SSH connection. 33 | /// 34 | [JsonIgnore] 35 | public IEnumerable? Keys { get; set; } 36 | 37 | 38 | /// 39 | /// Retrieves the connection information for establishing an SSH connection. 40 | /// 41 | /// 42 | /// The object representing the SSH connection information. 43 | /// 44 | public override ConnectionInfo GetConnectionInfo() 45 | { 46 | return new PrivateKeyConnectionInfo(Hostname, Port, Username, Keys.Select(e => e.GetSshNetKeyType()).ToArray()); 47 | } 48 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Credentials/PasswordConnectionCredentials.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:31 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using OpenSSH_GUI.Core.Interfaces.Credentials; 11 | using Renci.SshNet; 12 | 13 | namespace OpenSSH_GUI.Core.Lib.Credentials; 14 | 15 | /// **hostname**: The hostname or IP address of the remote server. 16 | /// **username**: The username used to authenticate with the remote server. 17 | /// **password**: The password used to authenticate with the remote server. 18 | /// **encryptedPassword**: (optional) Specifies whether the password is encrypted. The default value is `false`. 19 | public class PasswordConnectionCredentials( 20 | string hostname, 21 | string username, 22 | string password, 23 | bool encryptedPassword = false) 24 | : ConnectionCredentials(hostname, username, AuthType.Password), IPasswordConnectionCredentials 25 | { 26 | /// 27 | /// Represents connection credentials using password authentication. 28 | /// 29 | public string Password { get; set; } = password; 30 | 31 | /// 32 | /// Gets or sets a value indicating whether the password is encrypted. 33 | /// 34 | /// 35 | /// true if the password is encrypted; otherwise, false. 36 | /// 37 | public bool EncryptedPassword { get; set; } = encryptedPassword; 38 | 39 | /// 40 | /// Retrieves the connection information based on the provided credentials. 41 | /// 42 | /// The object representing the SSH connection information. 43 | public override ConnectionInfo GetConnectionInfo() 44 | { 45 | return new PasswordConnectionInfo(Hostname, Username, Password); 46 | } 47 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Keys/SSHKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:25 6 | 7 | #endregion 8 | 9 | using System.Diagnostics; 10 | using OpenSSH_GUI.Core.Interfaces.Keys; 11 | using OpenSSH_GUI.Core.Lib.Abstract; 12 | using OpenSSH_GUI.Core.Lib.Static; 13 | using SshNet.Keygen; 14 | 15 | namespace OpenSSH_GUI.Core.Lib.Keys; 16 | 17 | /// 18 | /// Represents an SSH key. 19 | /// 20 | public abstract class SshKey : KeyBase, ISshKey 21 | { 22 | /// 23 | /// Represents a SSH key. 24 | /// 25 | protected SshKey(string absoluteFilePath, string? password = null) : base(absoluteFilePath, password) 26 | { 27 | if (!FileOperations.Exists(AbsoluteFilePath)) 28 | throw new FileNotFoundException($"No such file: {AbsoluteFilePath}"); 29 | var outputOfProcess = ReadSshFile(ref absoluteFilePath).Split(' ').ToList(); 30 | outputOfProcess.RemoveRange(0, 2); 31 | outputOfProcess.Remove(outputOfProcess.Last()); 32 | Comment = string.Join(" ", outputOfProcess); 33 | Format = SshKeyFormat.OpenSSH; 34 | } 35 | 36 | /// 37 | /// Gets the comment associated with the SSH key. 38 | /// 39 | /// The comment. 40 | public string Comment { get; } 41 | 42 | 43 | /// 44 | /// Gets a value indicating whether the key is a Putty key. 45 | /// 46 | public bool IsPuttyKey => Format is not SshKeyFormat.OpenSSH; 47 | 48 | /// 49 | /// Exports the text representation of the SSH key. 50 | /// 51 | /// The text representation of the SSH key. 52 | public override string ExportTextOfKey() 53 | { 54 | return this is ISshPublicKey ? ExportOpenSshPublicKey()! : ExportOpenSshPrivateKey()!; 55 | } 56 | 57 | /// 58 | /// Reads the contents of an SSH file. 59 | /// 60 | /// The absolute file path of the SSH file to read. 61 | /// The contents of the SSH file. 62 | private string ReadSshFile(ref string filePath) 63 | { 64 | using var readerProcess = new Process(); 65 | readerProcess.StartInfo = new ProcessStartInfo 66 | { 67 | WindowStyle = ProcessWindowStyle.Hidden, 68 | RedirectStandardOutput = true, 69 | RedirectStandardError = true, 70 | CreateNoWindow = true, 71 | Arguments = $"-l -f {filePath}", 72 | FileName = "ssh-keygen" 73 | }; 74 | readerProcess.Start(); 75 | return readerProcess.StandardOutput.ReadToEnd(); 76 | } 77 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Keys/SSHKeyType.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:27 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using OpenSSH_GUI.Core.Extensions; 11 | using OpenSSH_GUI.Core.Interfaces.Keys; 12 | 13 | namespace OpenSSH_GUI.Core.Lib.Keys; 14 | 15 | /// 16 | /// Represents an SSH key type. 17 | /// 18 | public class SshKeyType : ISshKeyType 19 | { 20 | /// 21 | /// Represents a SSH key type. 22 | /// 23 | public SshKeyType(KeyType baseType) 24 | { 25 | BaseType = baseType; 26 | KeyTypeText = Enum.GetName(BaseType)!; 27 | var possibleBitSizes = BaseType.GetBitValues().ToList(); 28 | PossibleBitSizes = possibleBitSizes; 29 | HasDefaultBitSize = !PossibleBitSizes.Any(); 30 | CurrentBitSize = HasDefaultBitSize ? 0 : PossibleBitSizes.Max(); 31 | } 32 | 33 | public SshKeyType(string keyDefinition) 34 | { 35 | KeyTypeText = keyDefinition; 36 | BaseType = Enum.Parse( 37 | (KeyTypeText.StartsWith("ecdsa") 38 | ? KeyTypeText.Split('-')[0] 39 | : KeyTypeText.Split('-')[1]).ToUpper()); 40 | var possibleBitSizes = BaseType.GetBitValues().ToList(); 41 | PossibleBitSizes = possibleBitSizes; 42 | HasDefaultBitSize = !PossibleBitSizes.Any(); 43 | CurrentBitSize = HasDefaultBitSize ? 0 : PossibleBitSizes.Max(); 44 | } 45 | 46 | /// 47 | /// A class representing the base type of a SSH key. 48 | /// 49 | public KeyType BaseType { get; } 50 | 51 | public string KeyTypeText { get; } 52 | 53 | /// 54 | /// Gets a value indicating whether the SSH key type has a default bit size. 55 | /// 56 | /// 57 | /// The SSH key type may have a default bit size if there are no possible bit sizes defined for the key type. 58 | /// 59 | public bool HasDefaultBitSize { get; } 60 | 61 | /// 62 | /// Gets or sets the current bit size of the SSH key type. 63 | /// 64 | public int CurrentBitSize { get; set; } 65 | 66 | /// 67 | /// Represents a type of SSH key. 68 | /// 69 | public IEnumerable PossibleBitSizes { get; } 70 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Keys/SshPrivateKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:26 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Interfaces.Keys; 10 | 11 | namespace OpenSSH_GUI.Core.Lib.Keys; 12 | 13 | /// 14 | /// Represents an SSH private key. 15 | /// 16 | public class SshPrivateKey(string absoluteFilePath, string? password = null) 17 | : SshKey(absoluteFilePath, password), ISshPrivateKey; -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Keys/SshPublicKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:27 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Interfaces.Keys; 10 | using OpenSSH_GUI.Core.Lib.Static; 11 | 12 | namespace OpenSSH_GUI.Core.Lib.Keys; 13 | 14 | /// 15 | /// Represents an SSH public key. 16 | /// 17 | public class SshPublicKey(string absoluteFilePath, string? password = null) 18 | : SshKey(absoluteFilePath, password), ISshPublicKey 19 | { 20 | /// 21 | /// Represents a private key. 22 | /// 23 | public ISshKey PrivateKey { get; } = KeyFactory.FromPath(Path.ChangeExtension(absoluteFilePath, null), password)!; 24 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/KnownHosts/KnownHost.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:29 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Interfaces.KnownHosts; 10 | using ReactiveUI; 11 | 12 | namespace OpenSSH_GUI.Core.Lib.KnownHosts; 13 | 14 | /// 15 | /// Represents a known host in the OpenSSH GUI. 16 | /// 17 | public class KnownHost : ReactiveObject, IKnownHost 18 | { 19 | /// The _keys variable represents a collection of known host keys for a specific host. 20 | /// It is an instance variable of the KnownHost class. 21 | /// The _keys variable is a list of IKnownHostKey objects that store information about individual host keys. 22 | /// Each IKnownHostKey object represents a known host key and provides access to its properties such as key type, fingerprint, and deletion status. 23 | /// Usage: 24 | /// var knownHost = new KnownHost(knownHosts); 25 | /// knownHost.Keys = _keys; // Set the list of known host keys 26 | /// See IKnownHostKey and KnownHost classes for more information. 27 | /// / 28 | private List _keys = []; 29 | 30 | /// 31 | /// Represents a known host entry in the known_hosts file. 32 | /// 33 | public KnownHost(IGrouping knownHosts) 34 | { 35 | Host = knownHosts.Key; 36 | Keys = knownHosts.Select(e => new KnownHostKey(e.Replace($"{Host}", "").Trim()) as IKnownHostKey).ToList(); 37 | } 38 | 39 | /// 40 | /// Gets or sets the toggled state of the switch. 41 | /// 42 | /// 43 | /// When the switch is toggled: 44 | /// - If it was previously off, all known host keys are marked for deletion. 45 | /// - If it was previously on, all known host keys are unmarked for deletion. 46 | /// 47 | private bool SwitchToggled { get; set; } 48 | 49 | /// 50 | /// Represents a known host in the SSH known hosts file. 51 | /// 52 | public string Host { get; } 53 | 54 | /// 55 | /// Represents a known host that can be deleted in its entirety. 56 | /// 57 | public bool DeleteWholeHost => Keys.All(e => e.MarkedForDeletion); 58 | 59 | /// 60 | /// Represents a known host in the OpenSSH_GUI. 61 | /// 62 | public List Keys 63 | { 64 | get => _keys; 65 | set => this.RaiseAndSetIfChanged(ref _keys, value); 66 | } 67 | 68 | /// 69 | /// Toggles the marked for deletion flag of each within the list. 70 | /// If the property is true, it sets the flag to false for all keys. Otherwise, it sets 71 | /// the flag to true for all keys. 72 | /// 73 | public void KeysDeletionSwitch() 74 | { 75 | if (SwitchToggled) 76 | { 77 | foreach (var key in Keys) key.MarkedForDeletion = false; 78 | 79 | SwitchToggled = false; 80 | } 81 | else 82 | { 83 | foreach (var key in Keys) key.MarkedForDeletion = true; 84 | 85 | SwitchToggled = true; 86 | } 87 | } 88 | 89 | /// 90 | /// Retrieves all entries for a known host in the known hosts file. 91 | /// 92 | /// 93 | /// Returns a string containing all the entries for the known host. 94 | /// If the entire host is marked for deletion, returns the line ending character. 95 | /// 96 | public string GetAllEntries() 97 | { 98 | return DeleteWholeHost 99 | ? IKnownHostsFile.LineEnding 100 | : Keys 101 | .Where(e => !e.MarkedForDeletion) 102 | .Aggregate("", 103 | (current, knownHostsKey) => 104 | current + $"{Host} {knownHostsKey.EntryWithoutHost}{IKnownHostsFile.LineEnding}"); 105 | } 106 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/KnownHosts/KnownHostKey.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:30 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Core.Enums; 10 | using OpenSSH_GUI.Core.Interfaces.KnownHosts; 11 | using ReactiveUI; 12 | 13 | namespace OpenSSH_GUI.Core.Lib.KnownHosts; 14 | 15 | /// 16 | /// Represents a known host key in the OpenSSH GUI. 17 | /// 18 | public class KnownHostKey : ReactiveObject, IKnownHostKey 19 | { 20 | /// 21 | /// Represents a known host key. 22 | /// 23 | private bool _markedForDeletion; 24 | 25 | /// 26 | /// Represents a known host key in the OpenSSH GUI. 27 | /// 28 | public KnownHostKey(string entry) 29 | { 30 | EntryWithoutHost = entry; 31 | var splitted = EntryWithoutHost.Split(' '); 32 | TypeDeclarationInFile = splitted[0]; 33 | KeyType = Enum.Parse( 34 | TypeDeclarationInFile.StartsWith("ssh-") 35 | ? TypeDeclarationInFile.Replace("ssh-", "") 36 | : TypeDeclarationInFile.Split('-')[0], true); 37 | Fingerprint = splitted[1].Replace("\n", "").Replace("\r", ""); 38 | } 39 | 40 | /// 41 | /// Represents a known host key. 42 | /// 43 | private string TypeDeclarationInFile { get; } 44 | 45 | /// 46 | /// Represents the type of a known host key. 47 | /// 48 | public KeyType KeyType { get; } 49 | 50 | /// 51 | /// Represents a known host key. 52 | /// 53 | public string Fingerprint { get; } 54 | 55 | /// 56 | /// Represents a known host key without the host entry in the OpenSSH GUI. 57 | /// 58 | public string EntryWithoutHost { get; } 59 | 60 | /// 61 | /// Gets or sets a value indicating whether the known host key is marked for deletion. 62 | /// 63 | public bool MarkedForDeletion 64 | { 65 | get => _markedForDeletion; 66 | set => this.RaiseAndSetIfChanged(ref _markedForDeletion, value); 67 | } 68 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Misc/DirectoryCrawler.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:27 6 | 7 | #endregion 8 | 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.Logging; 11 | using OpenSSH_GUI.Core.Database.Context; 12 | using OpenSSH_GUI.Core.Database.DTO; 13 | using OpenSSH_GUI.Core.Extensions; 14 | using OpenSSH_GUI.Core.Interfaces.Keys; 15 | using OpenSSH_GUI.Core.Lib.Static; 16 | 17 | namespace OpenSSH_GUI.Core.Lib.Misc; 18 | 19 | /// 20 | /// Represents a directory crawler for searching and managing SSH keys. 21 | /// 22 | public static class DirectoryCrawler 23 | { 24 | private static ILogger _logger = null!; 25 | 26 | /// 27 | /// Provides the logger context for the DirectoryCrawler class. 28 | /// 29 | /// The logger instance to be used by the DirectoryCrawler class. 30 | public static void ProvideContext(ILogger logger) 31 | { 32 | _logger = logger; 33 | } 34 | 35 | /// 36 | /// Retrieves SSH keys from disk. 37 | /// 38 | /// 39 | /// Optional. Indicates whether to automatically convert PuTTY keys to OpenSSH format. Default is 40 | /// false. 41 | /// 42 | /// An enumerable collection of ISshKey representing the SSH keys. 43 | private static IEnumerable GetFromDisk(bool convert) 44 | { 45 | foreach (var filePath in Directory 46 | .EnumerateFiles(SshConfigFilesExtension.GetBaseSshPath(), "*", SearchOption.TopDirectoryOnly) 47 | .Where(e => e.EndsWith("pub") || e.EndsWith("ppk"))) 48 | { 49 | ISshKey? key = null; 50 | try 51 | { 52 | key = KeyFactory.FromPath(filePath); 53 | if (key is IPpkKey && convert) key = KeyFactory.ConvertToOppositeFormat(key, true); 54 | } 55 | catch (Exception ex) 56 | { 57 | _logger.LogError(ex, "Error while reading key from {path}", filePath); 58 | } 59 | 60 | if (key is null) continue; 61 | yield return key; 62 | } 63 | } 64 | 65 | /// 66 | /// Retrieves all SSH keys from disk or cache asynchronously, using a yield return method to lazily load the keys. 67 | /// 68 | /// Optional. Indicates whether to load keys from disk. Default is false. 69 | /// Optional. Indicates whether to purge passwords from cache. Default is false. 70 | /// An asynchronous enumerable collection of ISshKey representing the SSH keys. 71 | public static async IAsyncEnumerable GetAllKeysYield(bool loadFromDisk = false, 72 | bool purgePasswords = false) 73 | { 74 | await using var dbContext = new OpenSshGuiDbContext(); 75 | var cacheHasElements = await dbContext.KeyDtos.AnyAsync(); 76 | switch (loadFromDisk) 77 | { 78 | case false when cacheHasElements: 79 | foreach (var keyFromCache in dbContext.KeyDtos.Select(e => e.ToKey())) yield return keyFromCache!; 80 | yield break; 81 | case false when !cacheHasElements: 82 | loadFromDisk = true; 83 | break; 84 | } 85 | 86 | if (!loadFromDisk && cacheHasElements) yield break; 87 | { 88 | foreach (var key in GetFromDisk((await dbContext.Settings.FirstAsync()).ConvertPpkAutomatically)) 89 | { 90 | var found = await dbContext.KeyDtos.FirstOrDefaultAsync(e => 91 | e.AbsolutePath == key.AbsoluteFilePath); 92 | if (found is null) 93 | { 94 | await dbContext.KeyDtos.AddAsync(new SshKeyDto 95 | { 96 | AbsolutePath = key.AbsoluteFilePath, 97 | Password = key.Password, 98 | Format = key.Format 99 | }); 100 | } 101 | else 102 | { 103 | found.AbsolutePath = key.AbsoluteFilePath; 104 | found.Format = key.Format; 105 | if (found.Password is not null) key.Password = found.Password; 106 | if (purgePasswords) 107 | { 108 | key.Password = key.HasPassword ? "" : null; 109 | found.Password = key.HasPassword ? "" : null; 110 | } 111 | } 112 | 113 | await dbContext.SaveChangesAsync(); 114 | yield return key; 115 | } 116 | } 117 | } 118 | 119 | /// 120 | /// Retrieves all SSH keys from disk or cache. 121 | /// 122 | /// Optional. Indicates whether to load keys from disk. Default is false. 123 | /// An enumerable collection of ISshKey representing the SSH keys. 124 | public static IEnumerable GetAllKeys(bool loadFromDisk = false) 125 | { 126 | return GetAllKeysYield(loadFromDisk).ToBlockingEnumerable(); 127 | } 128 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Misc/SshKeyGenerateParams.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 18.05.2024 - 13:05:38 3 | // Last edit: 18.05.2024 - 13:05:38 4 | 5 | using OpenSSH_GUI.Core.Enums; 6 | using OpenSSH_GUI.Core.Extensions; 7 | using SshNet.Keygen; 8 | 9 | namespace OpenSSH_GUI.Core.Lib.Misc; 10 | 11 | public readonly record struct SshKeyGenerateParams 12 | { 13 | public SshKeyGenerateParams(KeyType type, 14 | SshKeyFormat format, 15 | string? fileName = null, 16 | string? filePath = null, 17 | string? password = null, 18 | string? comment = null, 19 | int? keyLength = null) 20 | { 21 | FileName = fileName ?? Path.ChangeExtension(Path.GetFileName("id_" + Path.GetTempFileName()), null); 22 | FilePath = filePath ?? SshConfigFilesExtension.GetBaseSshPath(); 23 | KeyType = type; 24 | Comment = comment ?? "by OpenSSH-GUI"; 25 | KeyFormat = format; 26 | KeyLength = keyLength ?? (int)type; 27 | Password = password; 28 | } 29 | 30 | /// 31 | /// Gets or sets the file name for the SSH key. 32 | /// 33 | public string FileName { get; } 34 | 35 | /// 36 | /// Represents the file path for an SSH key. 37 | /// 38 | /// Defaults to ~/.shh directory when null 39 | public string FilePath { get; } 40 | 41 | /// 42 | /// Represents the type of SSH key. 43 | /// 44 | public KeyType KeyType { get; } 45 | 46 | /// 47 | /// Represents a comment associated with an SSH key. 48 | /// 49 | /// Defaults to "by OpenSSH-GUI" when null 50 | public string Comment { get; } 51 | 52 | /// 53 | /// Specifies the format for SSH key generation. 54 | /// 55 | public SshKeyFormat KeyFormat { get; } 56 | 57 | /// 58 | /// Represents the length of an SSH key. 59 | /// 60 | public int KeyLength { get; } 61 | 62 | /// 63 | /// Represents the password associated with an SSH key. 64 | /// 65 | public string? Password { get; } 66 | 67 | public string FullFilePath => Path.Combine(FilePath, FileName); 68 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Settings/Settings.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:29 6 | 7 | #endregion 8 | 9 | using System.Reflection; 10 | 11 | namespace OpenSSH_GUI.Core.Lib.Settings; 12 | 13 | /// 14 | /// Represents a settings file for the application. 15 | /// 16 | public record Settings 17 | { 18 | /// 19 | /// The current version of the application. 20 | /// 21 | public string Version { get; set; } = 22 | $"v{(Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly()).GetName().Version?.ToString(3)}"; 23 | 24 | /// 25 | /// Whether to convert ppk files automatically. 26 | /// 27 | public bool ConvertPpkAutomatically { get; set; } = false; 28 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/Lib/Static/FileOperations.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 30.05.2024 - 12:05:06 5 | // Last edit: 30.05.2024 - 12:05:06 6 | 7 | #endregion 8 | 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.Extensions.Logging.Abstractions; 11 | using OpenSSH_GUI.Core.Enums; 12 | using OpenSSH_GUI.Core.Extensions; 13 | 14 | namespace OpenSSH_GUI.Core.Lib.Static; 15 | 16 | public static class FileOperations 17 | { 18 | public static void EnsureFilesAndFoldersExist(ILogger? logger = null) 19 | { 20 | logger??= NullLogger.Instance; 21 | SshConfigFilesExtension.ValidateDirectories(logger); 22 | foreach (var configFile in Enum.GetValues()) 23 | { 24 | var pathOfFile = configFile.GetPathOfFile(); 25 | try 26 | { 27 | if (!File.Exists(pathOfFile)) OpenOrCreate(pathOfFile); 28 | } 29 | catch (Exception e) 30 | { 31 | logger.LogError(e, "Error creating file {pathOfFile}", pathOfFile); 32 | } 33 | } 34 | } 35 | 36 | private static FileStreamOptions Options(FileMode mode) 37 | { 38 | var options = new FileStreamOptions 39 | { 40 | Access = FileAccess.ReadWrite, 41 | Mode = mode, 42 | Share = FileShare.ReadWrite 43 | }; 44 | if (Environment.OSVersion.Platform is PlatformID.Unix or PlatformID.MacOSX) 45 | { 46 | #pragma warning disable CA1416 47 | options.UnixCreateMode = mode 48 | is FileMode.Create 49 | or FileMode.CreateNew 50 | or FileMode.OpenOrCreate 51 | ? UnixFileMode.UserRead | UnixFileMode.UserWrite 52 | : null; 53 | #pragma warning restore CA1416 54 | } 55 | 56 | return options; 57 | } 58 | 59 | public static FileStream DeleteOldAndCreateNew(string filePath) 60 | { 61 | if (Exists(filePath)) Delete(filePath); 62 | return OpenOrCreate(filePath); 63 | } 64 | 65 | public static FileStream OpenOrCreate(string filePath) 66 | { 67 | return new FileStream(filePath, Options(FileMode.OpenOrCreate)); 68 | } 69 | 70 | public static FileStream OpenTruncated(string filePath) 71 | { 72 | return File.Open(filePath, Options(FileMode.Truncate)); 73 | } 74 | 75 | public static string[] ReadAllLines(string filePath) 76 | { 77 | return File.ReadAllLines(filePath); 78 | } 79 | 80 | public static bool Exists(string filePath) 81 | { 82 | return File.Exists(filePath); 83 | } 84 | 85 | public static void Move(string source, string destination, bool overwrite = false) 86 | { 87 | File.Move(source, destination, overwrite); 88 | } 89 | 90 | public static void Delete(string filePath) 91 | { 92 | if (File.Exists(filePath)) File.Delete(filePath); 93 | } 94 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.Core/OpenSSH_GUI.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 2.0.0 8 | 2.0.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /OpenSSH_GUI.Tests/OpenSSH_GUI.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | OpenSSHA_Tests 6 | enable 7 | enable 8 | 9 | false 10 | true 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | all 22 | runtime; build; native; contentfiles; analyzers; buildtransitive 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /OpenSSH_GUI.Tests/UnitTest1.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | namespace OpenSSHA_Tests; 10 | 11 | public class Tests 12 | { 13 | [SetUp] 14 | public void Setup() 15 | { 16 | } 17 | } -------------------------------------------------------------------------------- /OpenSSH_GUI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSSH_GUI", "OpenSSH_GUI\OpenSSH_GUI.csproj", "{833C761B-4BDF-402A-9B27-8995E2AD1DE3}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSSH_GUI.Core", "OpenSSH_GUI.Core\OpenSSH_GUI.Core.csproj", "{08608099-A6CD-405A-A3F0-70934D94D519}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSSH_GUI.Tests", "OpenSSH_GUI.Tests\OpenSSH_GUI.Tests.csproj", "{5B020E47-0F52-49C5-8D9F-23184B795186}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {833C761B-4BDF-402A-9B27-8995E2AD1DE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {833C761B-4BDF-402A-9B27-8995E2AD1DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {833C761B-4BDF-402A-9B27-8995E2AD1DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {833C761B-4BDF-402A-9B27-8995E2AD1DE3}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {833C761B-4BDF-402A-9B27-8995E2AD1DE3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU 20 | {08608099-A6CD-405A-A3F0-70934D94D519}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {08608099-A6CD-405A-A3F0-70934D94D519}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {08608099-A6CD-405A-A3F0-70934D94D519}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {08608099-A6CD-405A-A3F0-70934D94D519}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {5B020E47-0F52-49C5-8D9F-23184B795186}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {5B020E47-0F52-49C5-8D9F-23184B795186}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {5B020E47-0F52-49C5-8D9F-23184B795186}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {5B020E47-0F52-49C5-8D9F-23184B795186}.Release|Any CPU.Build.0 = Release|Any CPU 28 | EndGlobalSection 29 | EndGlobal 30 | -------------------------------------------------------------------------------- /OpenSSH_GUI.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | True 4 | False 5 | True 6 | False 7 | True 8 | 9 | 10 | 11 | 12 | 13 | 14 | False 15 | True 16 | True 17 | 18 | True 19 | False 20 | False 21 | False 22 | False -------------------------------------------------------------------------------- /OpenSSH_GUI/App.axaml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /OpenSSH_GUI/App.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:35 6 | 7 | #endregion 8 | 9 | using System; 10 | using System.IO; 11 | using System.Linq; 12 | using Avalonia; 13 | using Avalonia.Controls; 14 | using Avalonia.Controls.ApplicationLifetimes; 15 | using Avalonia.Markup.Xaml; 16 | using Avalonia.Media.Imaging; 17 | using Avalonia.Platform; 18 | using Microsoft.EntityFrameworkCore; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using Microsoft.Extensions.Logging; 21 | using OpenSSH_GUI.Core.Database.Context; 22 | using OpenSSH_GUI.Core.Extensions; 23 | using OpenSSH_GUI.Core.Lib.Misc; 24 | using OpenSSH_GUI.Core.Lib.Settings; 25 | using OpenSSH_GUI.Core.Lib.Static; 26 | using OpenSSH_GUI.ViewModels; 27 | using OpenSSH_GUI.Views; 28 | using Serilog; 29 | using Serilog.Events; 30 | 31 | namespace OpenSSH_GUI; 32 | 33 | public class App : Application 34 | { 35 | public static ServiceProvider ServiceProvider { get; private set; } 36 | public static WindowIcon WindowIcon => new (new Bitmap(AssetLoader.Open(new Uri("avares://OpenSSH_GUI/Assets/appicon.ico")))); 37 | 38 | public override void Initialize() 39 | { 40 | AvaloniaXamlLoader.Load(this); 41 | } 42 | 43 | public override void OnFrameworkInitializationCompleted() 44 | { 45 | ServiceProvider = BuildServiceCollection().BuildServiceProvider(); 46 | using (var db = new OpenSshGuiDbContext()) 47 | { 48 | db.Database.Migrate(); 49 | if (!db.Settings.Any()) db.Settings.Add(new Settings()); 50 | db.SaveChanges(); 51 | } 52 | 53 | InitAndOrPrepareServices(); 54 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 55 | desktop.MainWindow = new MainWindow 56 | { 57 | DataContext = new MainWindowViewModel() 58 | }; 59 | base.OnFrameworkInitializationCompleted(); 60 | } 61 | 62 | private void InitAndOrPrepareServices() 63 | { 64 | var logger = ServiceProvider.GetRequiredService>(); 65 | DirectoryCrawler.ProvideContext(logger); 66 | FileOperations.EnsureFilesAndFoldersExist(logger); 67 | } 68 | 69 | private ServiceCollection BuildServiceCollection() 70 | { 71 | var collection = new ServiceCollection(); 72 | var appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), 73 | AppDomain.CurrentDomain.FriendlyName); 74 | var logFilebasePath = Path.Combine(appDataPath, "logs"); 75 | if (!Directory.Exists(logFilebasePath)) Directory.CreateDirectory(logFilebasePath); 76 | var logFilePath = Path.Combine(logFilebasePath, $"{AppDomain.CurrentDomain.FriendlyName}.log"); 77 | var serilog = new LoggerConfiguration() 78 | .Enrich.FromLogContext() 79 | .WriteTo.File(logFilePath, LogEventLevel.Debug, rollingInterval: RollingInterval.Day) 80 | .CreateLogger(); 81 | // AddServices 82 | 83 | collection.AddLogging(e => e.AddSerilog(serilog, true)); 84 | 85 | // return ServiceCollection 86 | return collection; 87 | } 88 | 89 | private void CloseProgram(object? sender, EventArgs e) 90 | { 91 | if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) desktop.Shutdown(); 92 | } 93 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Assets/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/OpenSSH_GUI/Assets/appicon.ico -------------------------------------------------------------------------------- /OpenSSH_GUI/Assets/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/OpenSSH_GUI/Assets/appicon.png -------------------------------------------------------------------------------- /OpenSSH_GUI/Assets/avalonia-logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/OpenSSH_GUI/Assets/avalonia-logo.ico -------------------------------------------------------------------------------- /OpenSSH_GUI/Converters/PlatformIdConverter.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:40 6 | 7 | #endregion 8 | 9 | using System; 10 | using System.Globalization; 11 | using Avalonia.Data.Converters; 12 | 13 | namespace OpenSSH_GUI.Converters; 14 | 15 | public class PlatformIdConverter : IValueConverter 16 | { 17 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 18 | { 19 | if (Enum.GetName((PlatformID)value).Contains("Win", StringComparison.CurrentCultureIgnoreCase)) 20 | return "Windows"; 21 | return value is null ? value : Enum.GetName((PlatformID)value); 22 | } 23 | 24 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 25 | { 26 | return value is null ? value : Enum.Parse((string)value); 27 | } 28 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Converters/SingleSshKeyTypeConverter.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:41 6 | 7 | #endregion 8 | 9 | using System; 10 | using System.Globalization; 11 | using Avalonia.Data.Converters; 12 | using OpenSSH_GUI.Core.Enums; 13 | 14 | namespace OpenSSH_GUI.Converters; 15 | 16 | public class SingleSshKeyTypeConverter : IValueConverter 17 | { 18 | public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 19 | { 20 | return value is not null ? Enum.GetName(typeof(KeyType), (KeyType)value) : ""; 21 | } 22 | 23 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 24 | { 25 | return value is not null ? Enum.Parse((string)value) : ""; 26 | } 27 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Converters/SshKeyTypeConverter.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:40 6 | 7 | #endregion 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Globalization; 12 | using System.Linq; 13 | using Avalonia.Data.Converters; 14 | using OpenSSH_GUI.Core.Enums; 15 | using OpenSSH_GUI.Core.Interfaces.Keys; 16 | using OpenSSH_GUI.Core.Lib.Keys; 17 | 18 | namespace OpenSSH_GUI.Converters; 19 | 20 | public class SshKeyTypeConverter : IValueConverter 21 | { 22 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 23 | { 24 | if (value is not IEnumerable && value is ISshKeyType type) return type.BaseType; 25 | return (value as IEnumerable)!.Select(e => e.BaseType); 26 | } 27 | 28 | public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 29 | { 30 | if (value is not IEnumerable && value is KeyType type) return new SshKeyType(type); 31 | return (value as IEnumerable)!.Select(e => new SshKeyType(e)); 32 | } 33 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/OpenSSH_GUI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | net8.0 5 | enable 6 | true 7 | app.manifest 8 | true 9 | true 10 | true 11 | true 12 | Assets\appicon.ico 13 | false 14 | frequency403 15 | 2.0.0 16 | 2.0.0 17 | en 18 | OpenSSH-GUI.snk 19 | false 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | PublicResXFileCodeGenerator 49 | StringsAndTexts.Designer.cs 50 | 51 | 52 | 53 | 54 | 55 | True 56 | True 57 | StringsAndTexts.resx 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | OpenSSH GUI 67 | OpenSSH GUI 68 | frequency403.opensshgui 69 | 1.0.0 70 | APPL 71 | OpenSSHGui 72 | OpenSSHGui.icns 73 | NSApplication 74 | true 75 | 76 | 77 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Program.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:36 6 | 7 | #endregion 8 | 9 | using System; 10 | using Avalonia; 11 | using Avalonia.ReactiveUI; 12 | 13 | namespace OpenSSH_GUI; 14 | 15 | internal sealed class Program 16 | { 17 | // Initialization code. Don't use any Avalonia, third-party APIs or any 18 | // SynchronizationContext-reliant code before AppMain is called: things aren't initialized 19 | // yet and stuff might break. 20 | [STAThread] 21 | public static void Main(string[] args) 22 | { 23 | BuildAvaloniaApp() 24 | .StartWithClassicDesktopLifetime(args); 25 | } 26 | 27 | // Avalonia configuration, don't remove; also used by visual designer. 28 | private static AppBuilder BuildAvaloniaApp() 29 | { 30 | return AppBuilder.Configure() 31 | .UsePlatformDetect() 32 | .WithInterFont() 33 | .LogToTrace() 34 | .UseReactiveUI(); 35 | } 36 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Resources/Controls/BottomButtons.axaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 35 | 36 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Resources/Wrapper/WindowBase.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 27.05.2024 - 08:05:50 3 | // Last edit: 27.05.2024 - 08:05:51 4 | 5 | using System; 6 | using Avalonia.Controls; 7 | using Avalonia.Media.Imaging; 8 | using Avalonia.ReactiveUI; 9 | using OpenSSH_GUI.ViewModels; 10 | using ReactiveUI; 11 | 12 | namespace OpenSSH_GUI.Resources.Wrapper; 13 | 14 | public class WindowBase : ReactiveWindow where T : ViewModelBase 15 | { 16 | 17 | protected WindowBase() 18 | { 19 | Icon = App.WindowIcon; 20 | this.WhenActivated(d => 21 | { 22 | d(ViewModel!.Submit.Subscribe(Close)); 23 | d(ViewModel!.BooleanSubmit.Subscribe(Close)); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Resources/Wrapper/WindowInteraction.cs: -------------------------------------------------------------------------------- 1 | // File Created by: Oliver Schantz 2 | // Created: 21.05.2024 - 11:05:46 3 | // Last edit: 21.05.2024 - 11:05:47 4 | 5 | using System.Threading.Tasks; 6 | using Avalonia.Controls; 7 | using Avalonia.ReactiveUI; 8 | using OpenSSH_GUI.ViewModels; 9 | using OpenSSH_GUI.Views; 10 | using ReactiveUI; 11 | 12 | namespace OpenSSH_GUI.Resources.Wrapper; 13 | 14 | public static class WindowInteraction 15 | { 16 | public static async Task DialogMainWindow( 17 | IInteractionContext interaction, MainWindow windowOwner) 18 | where TViewModel : ViewModelBase, new() 19 | where TWindow : ReactiveWindow, new() 20 | { 21 | await DialogAnyWindow(interaction, windowOwner); 22 | } 23 | 24 | public static async Task DialogAnyWindow( 25 | IInteractionContext interaction, 26 | TWindowOwner windowOwner) 27 | where T : ViewModelBase, new() 28 | where TWindow : ReactiveWindow, new() 29 | where TWindowViewModel : ViewModelBase, new() 30 | where TWindowOwner : ReactiveWindow 31 | 32 | { 33 | var dialog = new TWindow 34 | { 35 | ViewModel = interaction.Input, 36 | WindowStartupLocation = WindowStartupLocation.CenterScreen 37 | }; 38 | var result = await dialog.ShowDialog(windowOwner); 39 | interaction.SetOutput(result); 40 | } 41 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewLocator.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:36 6 | 7 | #endregion 8 | 9 | using System; 10 | using Avalonia.Controls; 11 | using Avalonia.Controls.Templates; 12 | using OpenSSH_GUI.ViewModels; 13 | 14 | namespace OpenSSH_GUI; 15 | 16 | public class ViewLocator : IDataTemplate 17 | { 18 | public Control? Build(object? data) 19 | { 20 | if (data is null) 21 | return null; 22 | 23 | var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); 24 | var type = Type.GetType(name); 25 | 26 | if (type != null) 27 | { 28 | var control = (Control)Activator.CreateInstance(type)!; 29 | control.DataContext = data; 30 | return control; 31 | } 32 | 33 | return new TextBlock { Text = "Not Found: " + name }; 34 | } 35 | 36 | public bool Match(object? data) 37 | { 38 | return data is ViewModelBase; 39 | } 40 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/AddKeyWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:42 6 | 7 | #endregion 8 | 9 | using System; 10 | using System.Collections.ObjectModel; 11 | using System.Diagnostics; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Threading.Tasks; 15 | using Microsoft.Extensions.Logging; 16 | using OpenSSH_GUI.Core.Extensions; 17 | using OpenSSH_GUI.Core.Interfaces.Keys; 18 | using OpenSSH_GUI.Core.Lib.Misc; 19 | using OpenSSH_GUI.Core.Lib.Static; 20 | using ReactiveUI; 21 | using ReactiveUI.Validation.Abstractions; 22 | using ReactiveUI.Validation.Contexts; 23 | using ReactiveUI.Validation.Extensions; 24 | using ReactiveUI.Validation.Helpers; 25 | using SshNet.Keygen; 26 | 27 | namespace OpenSSH_GUI.ViewModels; 28 | 29 | public sealed class AddKeyWindowViewModel : ViewModelBase, IValidatableViewModel 30 | { 31 | private readonly ValidationHelper _keyNameValidationHelper = new(new ValidationContext()); 32 | private bool _createKey; 33 | 34 | private SshKeyFormat _keyFormat = SshKeyFormat.OpenSSH; 35 | 36 | private string _keyName = "id_rsa"; 37 | 38 | private ISshKeyType _selectedKeyType; 39 | 40 | private ObservableCollection _sshKeyTypes; 41 | 42 | public AddKeyWindowViewModel() 43 | { 44 | KeyNameValidationHelper = this.ValidationRule( 45 | e => e.KeyName, 46 | name => !FileOperations.Exists(Path.Combine(SshConfigFilesExtension.GetBaseSshPath(), name)), 47 | StringsAndTexts.AddKeyWindowFilenameError 48 | ); 49 | BooleanSubmit = ReactiveCommand.Create(b => 50 | { 51 | _createKey = b; 52 | if (!_createKey) return null; 53 | return !FileOperations.Exists(SshConfigFilesExtension.GetBaseSshPath() + Path.DirectorySeparatorChar + 54 | KeyName) 55 | ? this 56 | : null; 57 | }); 58 | _sshKeyTypes = new ObservableCollection(KeyTypeExtension.GetAvailableKeyTypes()); 59 | _selectedKeyType = _sshKeyTypes.First(); 60 | } 61 | 62 | public ValidationHelper KeyNameValidationHelper 63 | { 64 | get => _keyNameValidationHelper; 65 | private init => this.RaiseAndSetIfChanged(ref _keyNameValidationHelper, value); 66 | } 67 | 68 | public ISshKeyType SelectedKeyType 69 | { 70 | get => _selectedKeyType; 71 | set 72 | { 73 | try 74 | { 75 | KeyName = $"id_{Enum.GetName(value.BaseType)!.ToLower()}"; 76 | this.RaiseAndSetIfChanged(ref _selectedKeyType, value); 77 | } 78 | catch (Exception e) 79 | { 80 | Debug.WriteLine(e); 81 | } 82 | } 83 | } 84 | 85 | public ObservableCollection SshKeyTypes 86 | { 87 | get => _sshKeyTypes; 88 | set => this.RaiseAndSetIfChanged(ref _sshKeyTypes, value); 89 | } 90 | 91 | public SshKeyFormat KeyFormat 92 | { 93 | get => _keyFormat; 94 | set => this.RaiseAndSetIfChanged(ref _keyFormat, value); 95 | } 96 | 97 | public SshKeyFormat[] SshKeyFormats { get; } = Enum.GetValues(); 98 | 99 | 100 | public string KeyName 101 | { 102 | get => _keyName; 103 | set => this.RaiseAndSetIfChanged(ref _keyName, value); 104 | } 105 | 106 | public string Comment { get; set; } = $"{Environment.UserName}@{Environment.MachineName}"; 107 | public string Password { get; set; } = ""; 108 | 109 | public IValidationContext ValidationContext { get; } = new ValidationContext(); 110 | 111 | public async ValueTask RunKeyGen() 112 | { 113 | try 114 | { 115 | return await KeyFactory.GenerateNewAsync(new SshKeyGenerateParams( 116 | SelectedKeyType.BaseType, 117 | KeyFormat, 118 | string.IsNullOrWhiteSpace(KeyName) ? null : KeyName, 119 | null, 120 | string.IsNullOrWhiteSpace(Password) ? null : Password, 121 | string.IsNullOrWhiteSpace(Comment) ? null : Comment, 122 | SelectedKeyType.CurrentBitSize 123 | )); 124 | } 125 | catch (Exception e) 126 | { 127 | Logger.LogError(e, "Error creating key"); 128 | return null; 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/ApplicationSettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:41 6 | 7 | #endregion 8 | 9 | using System.Collections.Generic; 10 | using System.Collections.ObjectModel; 11 | using System.Linq; 12 | using System.Reactive.Linq; 13 | using Microsoft.EntityFrameworkCore; 14 | using MsBox.Avalonia; 15 | using OpenSSH_GUI.Core.Database.Context; 16 | using OpenSSH_GUI.Core.Interfaces.Credentials; 17 | using OpenSSH_GUI.Core.Interfaces.Keys; 18 | using OpenSSH_GUI.Core.Lib.Settings; 19 | using ReactiveUI; 20 | 21 | namespace OpenSSH_GUI.ViewModels; 22 | 23 | public sealed class ApplicationSettingsViewModel : ViewModelBase 24 | { 25 | private bool _convertPpkAutomatically; 26 | 27 | private List _knownServers; 28 | 29 | private readonly Settings _settings; 30 | 31 | public ObservableCollection Keys = []; 32 | public Interaction ShowEditEntry = new(); 33 | 34 | public ApplicationSettingsViewModel(ref ObservableCollection sshKeys) 35 | { 36 | Keys = sshKeys; 37 | using var dbContext = new OpenSshGuiDbContext(); 38 | _settings = dbContext.Settings.First(); 39 | _convertPpkAutomatically = _settings.ConvertPpkAutomatically; 40 | _knownServers = dbContext.ConnectionCredentialsDtos.Select(e => e.ToCredentials()).ToList(); 41 | 42 | BooleanSubmit = ReactiveCommand.CreateFromTask(async e => 43 | { 44 | if (!e) return this; 45 | await using var context = new OpenSshGuiDbContext(); 46 | var file = context.Settings.Update(_settings).Entity; 47 | file.ConvertPpkAutomatically = ConvertPpkAutomatically; 48 | var knownServerIds = KnownServers.Select(s => s.Id).ToList(); 49 | await context.ConnectionCredentialsDtos.Where(f => !knownServerIds.Contains(f.Id)).ExecuteDeleteAsync(); 50 | await context.SaveChangesAsync(); 51 | return this; 52 | }); 53 | } 54 | 55 | 56 | public List KnownServers 57 | { 58 | get => _knownServers; 59 | set => this.RaiseAndSetIfChanged(ref _knownServers, value); 60 | } 61 | 62 | public bool ConvertPpkAutomatically 63 | { 64 | get => _convertPpkAutomatically; 65 | set => this.RaiseAndSetIfChanged(ref _convertPpkAutomatically, value); 66 | } 67 | 68 | public ReactiveCommand RemoveServer => 69 | ReactiveCommand.Create(input => 70 | { 71 | var index = KnownServers.IndexOf(input); 72 | var copy = KnownServers.ToList(); 73 | copy.RemoveAt(index); 74 | KnownServers = copy; 75 | return this; 76 | }); 77 | 78 | public ReactiveCommand EditEntry => 79 | ReactiveCommand.CreateFromTask( 80 | async e => 81 | { 82 | if (e is IMultiKeyConnectionCredentials) 83 | { 84 | var box = MessageBoxManager.GetMessageBoxStandard( 85 | StringsAndTexts.ApplicationSettingsEditErrorBoxTitle, 86 | StringsAndTexts.ApplicationSettingsEditErrorBoxText); 87 | await box.ShowAsync(); 88 | return null; 89 | } 90 | 91 | var service = new EditSavedServerEntryViewModel(); 92 | service.SetValues(ref Keys, e); 93 | var result = await ShowEditEntry.Handle(service); 94 | if (result is null) return e; 95 | var list = KnownServers.ToList(); 96 | var index = list.IndexOf(e); 97 | list.RemoveAt(index); 98 | list.Insert(index, result.CredentialsToEdit); 99 | KnownServers = list; 100 | return result.CredentialsToEdit; // @TODO DoesNotShowKeys 101 | }); 102 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/EditAuthorizedKeysViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:43 6 | 7 | #endregion 8 | 9 | using System.Collections.ObjectModel; 10 | using System.Linq; 11 | using OpenSSH_GUI.Core.Enums; 12 | using OpenSSH_GUI.Core.Extensions; 13 | using OpenSSH_GUI.Core.Interfaces.AuthorizedKeys; 14 | using OpenSSH_GUI.Core.Interfaces.Keys; 15 | using OpenSSH_GUI.Core.Interfaces.Misc; 16 | using OpenSSH_GUI.Core.Lib.AuthorizedKeys; 17 | using ReactiveUI; 18 | 19 | namespace OpenSSH_GUI.ViewModels; 20 | 21 | public class EditAuthorizedKeysViewModel : ViewModelBase 22 | { 23 | private bool _addButtonEnabled; 24 | 25 | private ObservableCollection _publicKeys; 26 | 27 | private ISshKey? _selectedKey; 28 | 29 | private IServerConnection _serverConnection; 30 | 31 | public bool AddButtonEnabled 32 | { 33 | get => _addButtonEnabled; 34 | set => this.RaiseAndSetIfChanged(ref _addButtonEnabled, value); 35 | } 36 | 37 | public ISshKey? SelectedKey 38 | { 39 | get => _selectedKey; 40 | set 41 | { 42 | this.RaiseAndSetIfChanged(ref _selectedKey, value); 43 | UpdateAddButton(); 44 | } 45 | } 46 | 47 | public ObservableCollection PublicKeys 48 | { 49 | get => _publicKeys; 50 | set => this.RaiseAndSetIfChanged(ref _publicKeys, value); 51 | } 52 | 53 | public bool KeyAddPossible => PublicKeys.Count > 0; 54 | 55 | public IServerConnection ServerConnection 56 | { 57 | get => _serverConnection; 58 | set => this.RaiseAndSetIfChanged(ref _serverConnection, value); 59 | } 60 | 61 | public IAuthorizedKeysFile AuthorizedKeysFileLocal { get; } = 62 | new AuthorizedKeysFile(SshConfigFiles.Authorized_Keys.GetPathOfFile()); 63 | 64 | public IAuthorizedKeysFile AuthorizedKeysFileRemote { get; private set; } 65 | public ReactiveCommand AddKey { get; private set; } 66 | 67 | private void UpdateAddButton() 68 | { 69 | if (SelectedKey is null) return; 70 | AddButtonEnabled = !AuthorizedKeysFileRemote.AuthorizedKeys.Any(key => 71 | string.Equals(key.Fingerprint, SelectedKey.ExportAuthorizedKey().Fingerprint)); 72 | } 73 | 74 | public void SetConnectionAndKeys(ref IServerConnection serverConnection, 75 | ref ObservableCollection keys) 76 | { 77 | _serverConnection = serverConnection; 78 | AuthorizedKeysFileRemote = ServerConnection.GetAuthorizedKeysFromServer(); 79 | _publicKeys = keys; 80 | _selectedKey = PublicKeys.FirstOrDefault(); 81 | UpdateAddButton(); 82 | BooleanSubmit = ReactiveCommand.Create(e => 83 | { 84 | if (!e) return this; 85 | AuthorizedKeysFileLocal.PersistChangesInFile(); 86 | ServerConnection.WriteAuthorizedKeysChangesToServer(AuthorizedKeysFileRemote); 87 | return this; 88 | }); 89 | AddKey = ReactiveCommand.CreateFromTask(async e => 90 | { 91 | await AuthorizedKeysFileRemote.AddAuthorizedKeyAsync(e); 92 | UpdateAddButton(); 93 | return e; 94 | }); 95 | } 96 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/EditKnownHostsViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:45 6 | 7 | #endregion 8 | 9 | using System.Collections.ObjectModel; 10 | using System.Linq; 11 | using OpenSSH_GUI.Core.Enums; 12 | using OpenSSH_GUI.Core.Extensions; 13 | using OpenSSH_GUI.Core.Interfaces.KnownHosts; 14 | using OpenSSH_GUI.Core.Interfaces.Misc; 15 | using OpenSSH_GUI.Core.Lib.KnownHosts; 16 | using ReactiveUI; 17 | 18 | namespace OpenSSH_GUI.ViewModels; 19 | 20 | public class EditKnownHostsViewModel : ViewModelBase 21 | { 22 | private ObservableCollection _knownHostsLocal = []; 23 | 24 | private ObservableCollection _knownHostsRemote = []; 25 | 26 | public IServerConnection ServerConnection { get; private set; } 27 | 28 | private IKnownHostsFile KnownHostsFileLocal { get; set; } 29 | private IKnownHostsFile KnownHostsFileRemote { get; set; } 30 | 31 | public ObservableCollection KnownHostsRemote 32 | { 33 | get => _knownHostsRemote; 34 | private set => this.RaiseAndSetIfChanged(ref _knownHostsRemote, value); 35 | } 36 | 37 | public ObservableCollection KnownHostsLocal 38 | { 39 | get => _knownHostsLocal; 40 | private set => this.RaiseAndSetIfChanged(ref _knownHostsLocal, value); 41 | } 42 | 43 | public void SetServerConnection(ref IServerConnection connection) 44 | { 45 | ServerConnection = connection; 46 | KnownHostsFileLocal = new KnownHostsFile(SshConfigFiles.Known_Hosts.GetPathOfFile()); 47 | KnownHostsFileRemote = ServerConnection.GetKnownHostsFromServer(); 48 | KnownHostsLocal = new ObservableCollection(KnownHostsFileLocal.KnownHosts.OrderBy(e => e.Host)); 49 | KnownHostsRemote = new ObservableCollection(KnownHostsFileRemote.KnownHosts.OrderBy(e => e.Host)); 50 | BooleanSubmit = ReactiveCommand.CreateFromTask(async e => 51 | { 52 | if (!e) return this; 53 | KnownHostsFileLocal.SyncKnownHosts(KnownHostsLocal); 54 | if (ServerConnection.IsConnected) KnownHostsFileRemote.SyncKnownHosts(KnownHostsRemote); 55 | await KnownHostsFileLocal.UpdateFile(); 56 | if (ServerConnection.IsConnected) ServerConnection.WriteKnownHostsToServer(KnownHostsFileRemote); 57 | return this; 58 | }); 59 | } 60 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/EditSavedServerEntryViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:45 6 | 7 | #endregion 8 | 9 | using System.Collections.ObjectModel; 10 | using System.IO; 11 | using System.Linq; 12 | using OpenSSH_GUI.Core.Interfaces.Credentials; 13 | using OpenSSH_GUI.Core.Interfaces.Keys; 14 | using ReactiveUI; 15 | 16 | namespace OpenSSH_GUI.ViewModels; 17 | 18 | public sealed class EditSavedServerEntryViewModel : ViewModelBase 19 | { 20 | private string _password = ""; 21 | 22 | private ISshKey _selectedKey; 23 | 24 | public EditSavedServerEntryViewModel() 25 | { 26 | BooleanSubmit = 27 | ReactiveCommand.Create(e => 28 | { 29 | if (!e) return null; 30 | if (CredentialsToEdit is IPasswordConnectionCredentials pwcc) pwcc.Password = Password; 31 | if (CredentialsToEdit is IKeyConnectionCredentials kcc) kcc.Key = SelectedKey; 32 | return this; 33 | }); 34 | } 35 | 36 | public IConnectionCredentials CredentialsToEdit { get; set; } 37 | public ObservableCollection Keys { get; set; } 38 | 39 | public ISshKey SelectedKey 40 | { 41 | get => _selectedKey; 42 | set => this.RaiseAndSetIfChanged(ref _selectedKey, value); 43 | } 44 | 45 | public string Password 46 | { 47 | get => _password; 48 | set => this.RaiseAndSetIfChanged(ref _password, value); 49 | } 50 | 51 | public bool IsPasswordKey => CredentialsToEdit is IPasswordConnectionCredentials; 52 | 53 | public void SetValues(ref ObservableCollection keys, IConnectionCredentials credentials) 54 | { 55 | Keys = keys; 56 | CredentialsToEdit = credentials; 57 | if (CredentialsToEdit is IPasswordConnectionCredentials pwcc) Password = pwcc.Password; 58 | SelectedKey = CredentialsToEdit is IKeyConnectionCredentials kcc 59 | ? Keys.FirstOrDefault(e => string.Equals(kcc.Key.Fingerprint, e.Fingerprint)) 60 | : Keys.FirstOrDefault(); 61 | } 62 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/ExportWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:47 6 | 7 | #endregion 8 | 9 | using Avalonia; 10 | using Avalonia.Controls.ApplicationLifetimes; 11 | using ReactiveUI; 12 | 13 | namespace OpenSSH_GUI.ViewModels; 14 | 15 | public class ExportWindowViewModel : ViewModelBase 16 | { 17 | public ExportWindowViewModel() 18 | { 19 | BooleanSubmit = ReactiveCommand.Create(boolean => 20 | { 21 | if (!boolean) return null; 22 | if (Application.Current.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop) 23 | return null; 24 | var mainWindow = desktop.MainWindow; 25 | var clipboard = mainWindow.Clipboard; 26 | 27 | clipboard.SetTextAsync(Export).ConfigureAwait(false); 28 | return this; 29 | }); 30 | } 31 | 32 | public string WindowTitle { get; set; } = ""; 33 | public string Export { get; set; } = ""; 34 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:47 6 | 7 | #endregion 8 | 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Logging.Abstractions; 12 | using ReactiveUI; 13 | 14 | namespace OpenSSH_GUI.ViewModels; 15 | 16 | public class ViewModelBase() : ViewModelBase(App.ServiceProvider.GetRequiredService>()) where T : class 17 | { 18 | public ReactiveCommand Submit { get; set; } = ReactiveCommand.Create(e => null); 19 | public ReactiveCommand BooleanSubmit { get; set; } = ReactiveCommand.Create(e => null); 20 | } 21 | 22 | public class ViewModelBase(ILogger? logger = null) : ReactiveObject 23 | { 24 | protected ILogger Logger => logger ?? NullLogger.Instance; 25 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/AddKeyWindow.axaml: -------------------------------------------------------------------------------- 1 |  21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 32 | 38 | 40 | 48 | 50 | 55 | 56 | 58 | 63 | 65 | 70 | 82 | 83 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/AddKeyWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:37 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | using ReactiveUI; 12 | using ReactiveUI.Validation.Extensions; 13 | 14 | namespace OpenSSH_GUI.Views; 15 | 16 | public partial class AddKeyWindow : WindowBase 17 | { 18 | public AddKeyWindow() 19 | { 20 | InitializeComponent(); 21 | this.WhenActivated(d => 22 | { 23 | this.BindValidation(ViewModel, model => model.KeyName, 24 | window => window.KeyFileNameValidation.Text); 25 | // d(ViewModel!.Submit.Subscribe(Close)); 26 | }); 27 | } 28 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/ApplicationSettingsWindow.axaml: -------------------------------------------------------------------------------- 1 |  15 | 18 | 19 | 22 | 24 | 25 | 27 | 28 | 29 | 32 | 64 | 74 | 84 | 85 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/ApplicationSettingsWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:37 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | using ReactiveUI; 12 | 13 | namespace OpenSSH_GUI.Views; 14 | 15 | public partial class ApplicationSettingsWindow : WindowBase 16 | { 17 | public ApplicationSettingsWindow() 18 | { 19 | InitializeComponent(); 20 | this.WhenActivated(d => 21 | { 22 | d(ViewModel!.ShowEditEntry.RegisterHandler(async interaction => 23 | { 24 | var dialog = new EditSavedServerEntry 25 | { 26 | DataContext = interaction.Input, 27 | Title = interaction.Input.CredentialsToEdit.Display 28 | }; 29 | var result = await dialog.ShowDialog(this); 30 | interaction.SetOutput(result); 31 | })); 32 | }); 33 | } 34 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/ConnectToServerWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:38 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | 12 | namespace OpenSSH_GUI.Views; 13 | 14 | public partial class ConnectToServerWindow : WindowBase 15 | { 16 | public ConnectToServerWindow() 17 | { 18 | InitializeComponent(); 19 | } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/EditAuthorizedKeysWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:38 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | 12 | namespace OpenSSH_GUI.Views; 13 | 14 | public partial class EditAuthorizedKeysWindow : WindowBase 15 | { 16 | public EditAuthorizedKeysWindow() 17 | { 18 | InitializeComponent(); 19 | } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/EditKnownHostsWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:38 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | 12 | namespace OpenSSH_GUI.Views; 13 | 14 | public partial class EditKnownHostsWindow : WindowBase 15 | { 16 | public EditKnownHostsWindow() 17 | { 18 | InitializeComponent(); 19 | } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/EditSavedServerEntry.axaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 19 | 20 | 23 | 24 | 27 | 34 | 36 | 43 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/EditSavedServerEntry.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:39 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | 12 | namespace OpenSSH_GUI.Views; 13 | 14 | public partial class EditSavedServerEntry : WindowBase 15 | { 16 | public EditSavedServerEntry() 17 | { 18 | InitializeComponent(); 19 | } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/ExportWindow.axaml: -------------------------------------------------------------------------------- 1 |  16 | 17 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 28 | 39 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/ExportWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:39 6 | 7 | #endregion 8 | 9 | using OpenSSH_GUI.Resources.Wrapper; 10 | using OpenSSH_GUI.ViewModels; 11 | 12 | namespace OpenSSH_GUI.Views; 13 | 14 | public partial class ExportWindow : WindowBase 15 | { 16 | public ExportWindow() 17 | { 18 | InitializeComponent(); 19 | } 20 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/Views/MainWindow.axaml.cs: -------------------------------------------------------------------------------- 1 | #region CopyrightNotice 2 | 3 | // File Created by: Oliver Schantz 4 | // Created: 15.05.2024 - 00:05:44 5 | // Last edit: 15.05.2024 - 01:05:40 6 | 7 | #endregion 8 | 9 | using System.Threading.Tasks; 10 | using Avalonia.Controls; 11 | using Avalonia.ReactiveUI; 12 | using OpenSSH_GUI.ViewModels; 13 | using ReactiveUI; 14 | 15 | namespace OpenSSH_GUI.Views; 16 | 17 | public partial class MainWindow : ReactiveWindow 18 | { 19 | private const WindowStartupLocation DefaultWindowStartupLocation = WindowStartupLocation.CenterScreen; 20 | 21 | public MainWindow() 22 | { 23 | Icon = App.WindowIcon; 24 | InitializeComponent(); 25 | this.WhenActivated(action => action(ViewModel!.ShowCreate.RegisterHandler(DoShowAddKeyAsync))); 26 | this.WhenActivated(action => action(ViewModel!.ShowEditKnownHosts.RegisterHandler(DoShowEditKnownHostsAsync))); 27 | this.WhenActivated(action => action(ViewModel!.ShowExportWindow.RegisterHandler(DoShowExportWindowAsync))); 28 | this.WhenActivated(action => 29 | action(ViewModel!.ShowEditAuthorizedKeys.RegisterHandler(DoShowEditAuthorizedKeysWindowAsync))); 30 | this.WhenActivated(action => 31 | action(ViewModel!.ShowConnectToServerWindow.RegisterHandler(DoShowConnectToServerWindowAsync))); 32 | this.WhenActivated(action => 33 | action(ViewModel!.ShowAppSettings.RegisterHandler(DoShowApplicationSettingsWindowAsync))); 34 | } 35 | 36 | private async Task DoShowApplicationSettingsWindowAsync( 37 | IInteractionContext interaction) 38 | { 39 | var dialog = new ApplicationSettingsWindow 40 | { 41 | DataContext = interaction.Input, 42 | WindowStartupLocation = DefaultWindowStartupLocation 43 | }; 44 | interaction.SetOutput(await dialog.ShowDialog(this)); 45 | } 46 | 47 | private async Task DoShowConnectToServerWindowAsync( 48 | IInteractionContext interaction) 49 | { 50 | var dialog = new ConnectToServerWindow 51 | { 52 | DataContext = interaction.Input, 53 | WindowStartupLocation = DefaultWindowStartupLocation 54 | }; 55 | interaction.SetOutput(await dialog.ShowDialog(this)); 56 | } 57 | 58 | private async Task DoShowEditAuthorizedKeysWindowAsync( 59 | IInteractionContext interaction) 60 | { 61 | var dialog = new EditAuthorizedKeysWindow 62 | { 63 | DataContext = interaction.Input, 64 | WindowStartupLocation = DefaultWindowStartupLocation 65 | }; 66 | interaction.SetOutput(await dialog.ShowDialog(this)); 67 | } 68 | 69 | private async Task DoShowExportWindowAsync( 70 | IInteractionContext interaction) 71 | { 72 | var dialog = new ExportWindow 73 | { 74 | DataContext = interaction.Input, 75 | WindowStartupLocation = DefaultWindowStartupLocation 76 | }; 77 | interaction.SetOutput(await dialog.ShowDialog(this)); 78 | } 79 | 80 | private async Task DoShowAddKeyAsync(IInteractionContext interaction) 81 | { 82 | var dialog = new AddKeyWindow 83 | { 84 | DataContext = interaction.Input, 85 | WindowStartupLocation = DefaultWindowStartupLocation 86 | }; 87 | interaction.SetOutput(await dialog.ShowDialog(this)); 88 | } 89 | 90 | private async Task DoShowEditKnownHostsAsync( 91 | IInteractionContext interaction) 92 | { 93 | var dialog = new EditKnownHostsWindow 94 | { 95 | DataContext = interaction.Input, 96 | WindowStartupLocation = DefaultWindowStartupLocation 97 | }; 98 | interaction.SetOutput(await dialog.ShowDialog(this)); 99 | } 100 | } -------------------------------------------------------------------------------- /OpenSSH_GUI/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /images/AddKeyWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/AddKeyWindow.png -------------------------------------------------------------------------------- /images/AppSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/AppSettings.png -------------------------------------------------------------------------------- /images/ConnectToServerQuickConnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ConnectToServerQuickConnect.png -------------------------------------------------------------------------------- /images/ConnectToServerWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ConnectToServerWindow.png -------------------------------------------------------------------------------- /images/ConnectToServerWindowSuccess.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ConnectToServerWindowSuccess.png -------------------------------------------------------------------------------- /images/ConnectToServerWindowWithKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ConnectToServerWindowWithKey.png -------------------------------------------------------------------------------- /images/EditAuthorizedKeysWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/EditAuthorizedKeysWindow.png -------------------------------------------------------------------------------- /images/EditAuthorizedKeysWindowRemote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/EditAuthorizedKeysWindowRemote.png -------------------------------------------------------------------------------- /images/ExportKeyWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ExportKeyWindow.png -------------------------------------------------------------------------------- /images/FoundPasswordProtectedKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/FoundPasswordProtectedKey.png -------------------------------------------------------------------------------- /images/KnownHostsWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/KnownHostsWindow.png -------------------------------------------------------------------------------- /images/MainWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/MainWindow.png -------------------------------------------------------------------------------- /images/NewMainUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/NewMainUI.png -------------------------------------------------------------------------------- /images/ProvidePasswordPrompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ProvidePasswordPrompt.png -------------------------------------------------------------------------------- /images/SettingsContextMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/SettingsContextMenu.png -------------------------------------------------------------------------------- /images/ShowForgetPws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/ShowForgetPws.png -------------------------------------------------------------------------------- /images/Sorted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/Sorted.png -------------------------------------------------------------------------------- /images/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/tooltip.png -------------------------------------------------------------------------------- /images/tooltipKey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/tooltipKey.png -------------------------------------------------------------------------------- /images/tooltipServer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frequency403/OpenSSH-GUI/5455133b1a58a9ac0d7df7335265dbbfb789d744/images/tooltipServer.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /qodana.yaml: -------------------------------------------------------------------------------- 1 | version: "1.0" 2 | linter: jetbrains/qodana-dotnet:2024.1 3 | profile: 4 | name: qodana.recommended 5 | include: 6 | - name: CheckDependencyLicenses --------------------------------------------------------------------------------