diff --git a/src/UpgradeLog.htm b/src/UpgradeLog.htm new file mode 100644 index 0000000..917922d Binary files /dev/null and b/src/UpgradeLog.htm differ diff --git a/src/VsLinuxDebugger/Commands.Impl.cs b/src/VsLinuxDebugger/Commands.Impl.cs index da29180..e7bb7b2 100644 --- a/src/VsLinuxDebugger/Commands.Impl.cs +++ b/src/VsLinuxDebugger/Commands.Impl.cs @@ -183,7 +183,8 @@ private UserOptions ToUserOptions() UserPrivateKeyPassword = VsixPackage.VsixOptions.UserPrivateKeyPassword, UserName = VsixPackage.VsixOptions.UserName, UserPass = VsixPackage.VsixOptions.UserPass, - UserGroupName = VsixPackage.VsixOptions.UserGroupName, + UserGroupName = VsixPackage.VsixOptions.UserGroupName, + UseSSHExeEnabled = VsixPackage.VsixOptions.UseSSHExeEnabled }; } } diff --git a/src/VsLinuxDebugger/Core/LaunchBuilder.cs b/src/VsLinuxDebugger/Core/LaunchBuilder.cs index eb2c6b9..cac762e 100644 --- a/src/VsLinuxDebugger/Core/LaunchBuilder.cs +++ b/src/VsLinuxDebugger/Core/LaunchBuilder.cs @@ -136,18 +136,24 @@ public string GenerateLaunchJson(bool vsdbgLogging = false) ////{ string plinkPath = string.Empty; - - // Adapter Path: - // PLink.exe - Use manual path or embedded - if (!string.IsNullOrEmpty(_opts.LocalPLinkPath) && File.Exists(_opts.LocalPLinkPath)) - { - plinkPath = _opts.LocalPLinkPath; - } - else - { - plinkPath = Path.Combine(GetExtensionDirectory(), "plink.exe").Trim('"'); + + if (_opts.UseSSHExeEnabled) + { + plinkPath = "ssh.exe"; + } + else + { + // Adapter Path: + // PLink.exe - Use manual path or embedded + if (!string.IsNullOrEmpty(_opts.LocalPLinkPath) && File.Exists(_opts.LocalPLinkPath)) + { + plinkPath = _opts.LocalPLinkPath; + } + else + { + plinkPath = Path.Combine(GetExtensionDirectory(), "plink.exe").Trim('"'); + } } - // Adapter Arguments: // NOTE: // 1. SSH Private Key ("-i PPK") fails with PLINK. Must use manual password until this is resolved. @@ -161,16 +167,33 @@ public string GenerateLaunchJson(bool vsdbgLogging = false) ////var sshPassword = !_opts.UserPrivateKeyEnabled //// ? $"-pw {RemoteUserPass}" //// : $"-i \"{_opts.UserPrivateKeyPath}{strictKeyChecking}\""; - // - var sshPassword = $"-pw {RemoteUserPass}"; + string sshPassword = ""; + + if(_opts.UseSSHExeEnabled) + { + sshPassword = ""; //nothing to do, we assume that c:\users\[user]\.ssh\id_rsa exists + } + else + { + sshPassword = $"-pw {RemoteUserPass}"; + } // TODO: Figure out why "-i " isn't working. if (string.IsNullOrEmpty(RemoteUserPass)) Logger.Output("You must provide a User Password to debug."); - - var adapter = plinkPath; - var adapterArgs = $"-ssh {sshPassword} {sshEndpoint} -batch -T {_opts.RemoteVsDbgFullPath} {vsdbgLogPath}"; - //// adapterArgs = $"-ssh {sshPassword} {sshEndpoint} -batch -T {RemoteVsDbgFullPath} --interpreter=vscode {vsdbgLogPath}"; + + string adapter = plinkPath; + string adapterArgs = ""; + + if (_opts.UseSSHExeEnabled) + { + adapterArgs = $"{sshPassword} {sshEndpoint} -T {_opts.RemoteVsDbgFullPath} {vsdbgLogPath}"; + //// adapterArgs = $"-ssh {sshPassword} {sshEndpoint} -batch -T {RemoteVsDbgFullPath} --interpreter=vscode {vsdbgLogPath}"; + } + else + { + adapterArgs= $"-ssh {sshPassword} {sshEndpoint} -T {_opts.RemoteVsDbgFullPath} {vsdbgLogPath}"; + } return (adapter, adapterArgs); } diff --git a/src/VsLinuxDebugger/Core/RemoteDebugger.cs b/src/VsLinuxDebugger/Core/RemoteDebugger.cs index 560122f..4f119a2 100644 --- a/src/VsLinuxDebugger/Core/RemoteDebugger.cs +++ b/src/VsLinuxDebugger/Core/RemoteDebugger.cs @@ -12,7 +12,9 @@ namespace VsLinuxDebugger.Core { public class RemoteDebugger : IDisposable { - private const string DebugAdapterHost = "DebugAdapterHost.Launch"; + private const string DebugAdapterHost = "DebugAdapterHost.Launch"; + private const string DebugAdapterHostLogging = "DebugAdapterHost.Logging"; + private const string DebugAdapterHostLoggingOnOutputWindow = "/On /OutputWindow"; private const string DebugAdapterLaunchJson = "/LaunchJson:"; private bool _buildSuccessful; @@ -192,7 +194,10 @@ private void BuildDebugAttacher() Logger.Output($"- launch.json path: '{_launchJsonPath}'"); Logger.Output($"- DebugAdapterHost.Launch /LaunchJson:\"{_launchJsonPath}\""); - DTE2 dte2 = (DTE2)Package.GetGlobalService(typeof(SDTE)); + DTE2 dte2 = (DTE2)Package.GetGlobalService(typeof(SDTE)); + //Enable Logging for the Debugger output + dte2.ExecuteCommand(DebugAdapterHostLogging, $"{DebugAdapterHostLoggingOnOutputWindow}"); + dte2.ExecuteCommand(DebugAdapterHost, $"{DebugAdapterLaunchJson}\"{_launchJsonPath}\""); // launchConfigName = "Debug on Linux"; diff --git a/src/VsLinuxDebugger/Core/RsaSha256Util.cs b/src/VsLinuxDebugger/Core/RsaSha256Util.cs new file mode 100644 index 0000000..4174069 --- /dev/null +++ b/src/VsLinuxDebugger/Core/RsaSha256Util.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Renci; +using Renci.SshNet; +using Renci.SshNet.Sftp; +using Renci.SshNet.Security; +using Renci.SshNet.Common; +using Renci.SshNet.Security.Cryptography.Ciphers; +using Renci.SshNet.Security.Cryptography; + +using System.Security.Cryptography; +using System.Reflection; +namespace VsLinuxDebugger.Core +{ + /// + /// Based on https://github.com/sshnet/SSH.NET/blob/1d5d58e17c68a2f319c51e7f938ce6e964498bcc/src/Renci.SshNet/Security/Cryptography/RsaDigitalSignature.cs#L12 + /// + /// With following changes: + /// + /// - OID changed to sha2-256 + /// - hash changed from sha1 to sha2-256 + /// + public class RsaSha256DigitalSignature : CipherDigitalSignature, IDisposable + { + private HashAlgorithm _hash; + + public RsaSha256DigitalSignature(RsaWithSha256SignatureKey rsaKey) + // custom OID + : base(new ObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 1), new RsaCipher(rsaKey)) + { + // custom + _hash = SHA256.Create(); + } + + protected override byte[] Hash(byte[] input) + { + return _hash.ComputeHash(input); + } + + private bool _isDisposed; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + return; + + if (disposing) + { + var hash = _hash; + if (hash != null) + { + hash.Dispose(); + _hash = null; + } + + _isDisposed = true; + } + } + + ~RsaSha256DigitalSignature() + { + Dispose(false); + } + } + + /// + /// Utility class which allows ssh.net to connect to servers using ras-sha2-256 + /// + public static class RsaSha256Util + { + public static void SetupConnection(ConnectionInfo connection) + { + connection.HostKeyAlgorithms["rsa-sha2-256"] = data => new KeyHostAlgorithm("rsa-sha2-256", new RsaKey(), data); + } + + /// + /// Converts key file to rsa key with sha2-256 signature + /// Due to lack of constructor: https://github.com/sshnet/SSH.NET/blob/bc99ada7da3f05f50d9379f2644941d91d5bf05a/src/Renci.SshNet/PrivateKeyFile.cs#L86 + /// We do that in place + /// + /// + /// + public static void ConvertToKeyWithSha256Signature(PrivateKeyFile keyFile) + { + var oldKeyHostAlgorithm = keyFile.HostKey as KeyHostAlgorithm; + if (oldKeyHostAlgorithm == null) + { + throw new ArgumentNullException(nameof(oldKeyHostAlgorithm)); + } + + var oldRsaKey = oldKeyHostAlgorithm.Key as RsaKey; + if (oldRsaKey == null) + { + throw new ArgumentNullException(nameof(oldRsaKey)); + } + + var newRsaKey = new RsaWithSha256SignatureKey(oldRsaKey.Modulus, oldRsaKey.Exponent, oldRsaKey.D, oldRsaKey.P, oldRsaKey.Q, + oldRsaKey.InverseQ); + + UpdatePrivateKeyFile(keyFile, newRsaKey); + } + + private static void UpdatePrivateKeyFile(PrivateKeyFile keyFile, RsaWithSha256SignatureKey key) + { + var keyHostAlgorithm = new KeyHostAlgorithm(key.ToString(), key); + + var hostKeyProperty = typeof(PrivateKeyFile).GetProperty(nameof(PrivateKeyFile.HostKey)); + hostKeyProperty.SetValue(keyFile, keyHostAlgorithm); + + var keyField = typeof(PrivateKeyFile).GetField("_key", BindingFlags.NonPublic | BindingFlags.Instance); + keyField.SetValue(keyFile, key); + } + } + + public class RsaWithSha256SignatureKey : RsaKey + { + public RsaWithSha256SignatureKey(BigInteger modulus, BigInteger exponent, BigInteger d, BigInteger p, BigInteger q, + BigInteger inverseQ) : base(modulus, exponent, d, p, q, inverseQ) + { + } + + private RsaSha256DigitalSignature _digitalSignature; + + protected override DigitalSignature DigitalSignature + { + get + { + if (_digitalSignature == null) + { + _digitalSignature = new RsaSha256DigitalSignature(this); + } + + return _digitalSignature; + } + } + + public override string ToString() + { + return "rsa-sha2-256"; + } + } +} diff --git a/src/VsLinuxDebugger/Core/SshTool.cs b/src/VsLinuxDebugger/Core/SshTool.cs index 662c765..a6414d3 100644 --- a/src/VsLinuxDebugger/Core/SshTool.cs +++ b/src/VsLinuxDebugger/Core/SshTool.cs @@ -156,7 +156,7 @@ public async Task CleanFolderAsync(string path) public async Task ConnectAsync() { PrivateKeyFile keyFile = null; - + ConnectionInfo conn = null; try { if (_info.PrivateKeyEnabled) @@ -164,7 +164,14 @@ public async Task ConnectAsync() if (string.IsNullOrEmpty(_info.PrivateKeyPassword)) keyFile = new PrivateKeyFile(_info.PrivateKeyPath); else - keyFile = new PrivateKeyFile(_info.PrivateKeyPath, _info.PrivateKeyPassword); + keyFile = new PrivateKeyFile(_info.PrivateKeyPath, _info.PrivateKeyPassword); + + // adds rsa-sha2-256 + RsaSha256Util.ConvertToKeyWithSha256Signature(keyFile); + var authenticationMethodRsa = new PrivateKeyAuthenticationMethod(_info.UserName, keyFile); + conn = new ConnectionInfo(_info.Host, _info.Port, _info.UserName, authenticationMethodRsa); + RsaSha256Util.SetupConnection(conn); + } } catch (Exception ex) @@ -175,10 +182,15 @@ public async Task ConnectAsync() } try - { - _ssh = (_info.PrivateKeyEnabled && File.Exists(_info.PrivateKeyPath)) - ? new SshClient(_info.Host, _info.Port, _info.UserName, keyFile) - : new SshClient(_info.Host, _info.Port, _info.UserName, _info.UserPass); + { + if (_info.PrivateKeyEnabled && File.Exists(_info.PrivateKeyPath)) + { + _ssh = new SshClient(conn); + } + else + { + _ssh = new SshClient(_info.Host, _info.Port, _info.UserName, _info.UserPass); + } await Task.Run(() => _ssh.Connect()); } @@ -189,19 +201,29 @@ public async Task ConnectAsync() } try - { - var sftpClient = (keyFile == null) - ? new SftpClient(_info.Host, _info.Port, _info.UserName, _info.UserPass) - : new SftpClient(_info.Host, _info.Port, _info.UserName, keyFile); - - sftpClient.Connect(); - _sftp = sftpClient; + { + if (_info.PrivateKeyEnabled && File.Exists(_info.PrivateKeyPath)) + { + _sftp = new SftpClient(conn); + } + else + { + _sftp = new SftpClient(_info.Host, _info.Port, _info.UserName, _info.UserPass); + } + + _sftp.Connect(); + } catch (Exception) - { - _scp = (keyFile == null) - ? new ScpClient(_info.Host, _info.Port, _info.UserName, _info.UserPass) - : new ScpClient(_info.Host, _info.Port, _info.UserName, keyFile); + { + if (_info.PrivateKeyEnabled && File.Exists(_info.PrivateKeyPath)) + { + _scp = new ScpClient(conn); + } + else + { + _scp = new ScpClient(_info.Host, _info.Port, _info.UserName, _info.UserPass); + } _scp.Connect(); } diff --git a/src/VsLinuxDebugger/Core/UserOptions.cs b/src/VsLinuxDebugger/Core/UserOptions.cs index 0e622b3..5826c9c 100644 --- a/src/VsLinuxDebugger/Core/UserOptions.cs +++ b/src/VsLinuxDebugger/Core/UserOptions.cs @@ -18,7 +18,7 @@ public class UserOptions /// Base path to VSDBG (i.e. `~/.vsdbg`). public string RemoteVsDbgBasePath { get; set; } /// Full path to VS Debugger. - public string RemoteVsDbgFullPath => LinuxPath.Combine(RemoteVsDbgBasePath, Constants.VS2022); + public string RemoteVsDbgFullPath => LinuxPath.Combine(RemoteVsDbgBasePath, Constants.VS2022, Constants.AppVSDbg); public bool UseCommandLineArgs { get; set; } public bool UsePublish { get; set; } @@ -28,6 +28,8 @@ public class UserOptions public string UserPass { get; set; } public bool UserPrivateKeyEnabled { get; set; } public string UserPrivateKeyPath { get; set; } - public string UserPrivateKeyPassword { get; set; } + public string UserPrivateKeyPassword { get; set; } + + public bool UseSSHExeEnabled { get; set; } = false; } } diff --git a/src/VsLinuxDebugger/OptionsPages/OptionsPage.DotNet.cs b/src/VsLinuxDebugger/OptionsPages/OptionsPage.DotNet.cs index 66f0e06..685ed5e 100644 --- a/src/VsLinuxDebugger/OptionsPages/OptionsPage.DotNet.cs +++ b/src/VsLinuxDebugger/OptionsPages/OptionsPage.DotNet.cs @@ -22,8 +22,8 @@ public partial class OptionsPage : DialogPage public string RemoteDeployBasePath { get; set; } = $"./VSLinuxDbg"; // "LinuxDbg" [Category(RemoteDebugger)] - [DisplayName(".NET Path")] - [Description("Path of .NET on remote machine. (Samples: `dotnet`, `~/.dotnet/dotnet`)")] + [DisplayName(".NET executable")] + [Description("Path of the .NET executable on remote machine. (Samples: `dotnet`, `~/.dotnet/dotnet`)")] public string RemoteDotNetPath { get; set; } = Constants.DefaultDotNetPath; [Category(RemoteDebugger)] diff --git a/src/VsLinuxDebugger/OptionsPages/OptionsPage.Ssh.cs b/src/VsLinuxDebugger/OptionsPages/OptionsPage.Ssh.cs index 2d40730..6adb682 100644 --- a/src/VsLinuxDebugger/OptionsPages/OptionsPage.Ssh.cs +++ b/src/VsLinuxDebugger/OptionsPages/OptionsPage.Ssh.cs @@ -44,11 +44,29 @@ public partial class OptionsPage : DialogPage [Description("Private key file.")] public string UserPrivateKeyPath { get; set; } = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), - ".ssh\\id_rsa"); + ".ssh\\id_rsa"); [Category(Credientials)] [DisplayName("SSH Private Key Password (optional)")] [Description("Private key password (only if it was set).")] - public string UserPrivateKeyPassword { get; set; } = ""; + public string UserPrivateKeyPassword { get; set; } = ""; + + [Category(Credientials)] + [DisplayName("Use SSH.exe with integrated user/[..]/.ssh/id_rsa instead of PLINK")] + [Description("Use SSH.exe with integrated user/[..]/.ssh/id_rsa instead of PLINK")] + public bool UseSSHExeEnabled { get; set; } = false; + + /*[Category(Credientials)] + [DisplayName("PLINK PPK Key File Enabled")] + [Description("Use SSH Key for connecting to remote machine.")] + public bool UserPlinkPrivateKeyEnabled { get; set; } = false; + + [Category(Credientials)] + [DisplayName("SSH Private Key File (optional)")] + [Description("Private key file.")] + public string UserPlinkPrivateKeyPath { get; set; } = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + ".ssh\\id_rsa"); + */ } } diff --git a/src/VsLinuxDebugger/Properties/AssemblyInfo.cs b/src/VsLinuxDebugger/Properties/AssemblyInfo.cs index dd85dda..b9ab1db 100644 --- a/src/VsLinuxDebugger/Properties/AssemblyInfo.cs +++ b/src/VsLinuxDebugger/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.0.1.0")] -[assembly: AssemblyFileVersion("2.0.1.0")] +[assembly: AssemblyVersion("2.0.3.1")] +[assembly: AssemblyFileVersion("2.0.3.1")] diff --git a/src/VsLinuxDebugger/VsLinuxDebugger.csproj b/src/VsLinuxDebugger/VsLinuxDebugger.csproj index f09925a..11788be 100644 --- a/src/VsLinuxDebugger/VsLinuxDebugger.csproj +++ b/src/VsLinuxDebugger/VsLinuxDebugger.csproj @@ -1,157 +1,160 @@ - - - - 17.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - Debug - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {C204143E-FDE0-4F29-976B-A0C8AC798BAD} - Library - Properties - VsLinuxDebugger - VsLinuxDebugger - v4.7.2 - true - true - true - false - false - true - true - Program - $(DevEnvDir)devenv.exe - /rootsuffix Exp - - - true - full - false - ..\..\output\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\output\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - Component - - - Component - - - Component - - - - - - - - Always - true - - - Designer - - - - - - - - - - - - - - - - 0.30.1 - - - 2020.0.2 - - - 6.0.2 - - - - - Menus.ctmenu - - - - - Always - true - - - true - - - true - - - true - - - Always - true - - - - - + + + + 17.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {C204143E-FDE0-4F29-976B-A0C8AC798BAD} + Library + Properties + VsLinuxDebugger + VsLinuxDebugger + v4.7.2 + 8.0 + true + true + true + false + false + true + true + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + ..\..\output\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\..\output\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + Component + + + Component + + + Component + + + + + + + + Always + true + + + Designer + + + + + + + + + + + + + + + + 0.30.1 + + + 2020.0.2 + + + 6.0.2 + + + + + Menus.ctmenu + + + + + Always + true + + + true + + + true + + + true + + + Always + true + + + + + - - - https://the.earth.li/~sgtatham/putty/latest/w64/plink.exe - - - - - - - - - true - Always - - + --> + + + https://the.earth.li/~sgtatham/putty/latest/w64/plink.exe + + + + + + + + + true + Always + + \ No newline at end of file diff --git a/src/VsLinuxDebugger/source.extension.vsixmanifest b/src/VsLinuxDebugger/source.extension.vsixmanifest index 1cdc942..1d25718 100644 --- a/src/VsLinuxDebugger/source.extension.vsixmanifest +++ b/src/VsLinuxDebugger/source.extension.vsixmanifest @@ -2,34 +2,35 @@ - - - VS Linux Debugger - Remotely deploy and debug your .NET apps visa SSH on your Linux device using Visual Studio 2022. Works with popular Linux distrobutions such as Ubuntu, Raspberry Pi, and more! - https://github.com/SuessLabs/VsLinuxDebug - LICENSE.txt - ..\..\readme.md - ..\..\release-notes.md - Resources\TuxDebug.png - debug; build; remote debug; vsdbg; linux; xamarin; rpi; rpi4; remotedebug; remote; debugger; linux debug; net6; dotnet; raspberry pi; ubuntu; - - - - amd64 - - - - - - - - - - - + + + VS Linux Debugger + Remotely deploy and debug your .NET apps visa SSH on your Linux device using Visual Studio 2022. Works with popular Linux distrobutions such as Ubuntu, Raspberry Pi, and more! + https://github.com/SuessLabs/VsLinuxDebug + LICENSE.txt + ..\..\readme.md + ..\..\release-notes.md + Resources\TuxDebug.png + debug; build; remote debug; vsdbg; linux; xamarin; rpi; rpi4; remotedebug; remote; debugger; linux debug; net6; dotnet; raspberry pi; ubuntu; + true + + + + amd64 + + + + + + + + + + +