Skip to content

Commit

Permalink
[SuessLabs#32][SuessLabs#61] added vsdbg to launch.json, added rsa-sh…
Browse files Browse the repository at this point in the history
…a2-256 for ssh.net, added ssh.exe adapter,.. working version
  • Loading branch information
zeusware committed Jul 2, 2023
1 parent 55034d2 commit e4301ce
Show file tree
Hide file tree
Showing 12 changed files with 451 additions and 225 deletions.
Binary file added src/UpgradeLog.htm
Binary file not shown.
3 changes: 2 additions & 1 deletion src/VsLinuxDebugger/Commands.Impl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
}
}
Expand Down
57 changes: 40 additions & 17 deletions src/VsLinuxDebugger/Core/LaunchBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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 <keyfile>" 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);
}
Expand Down
9 changes: 7 additions & 2 deletions src/VsLinuxDebugger/Core/RemoteDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down
151 changes: 151 additions & 0 deletions src/VsLinuxDebugger/Core/RsaSha256Util.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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
/// </summary>
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);
}
}

/// <summary>
/// Utility class which allows ssh.net to connect to servers using ras-sha2-256
/// </summary>
public static class RsaSha256Util
{
public static void SetupConnection(ConnectionInfo connection)
{
connection.HostKeyAlgorithms["rsa-sha2-256"] = data => new KeyHostAlgorithm("rsa-sha2-256", new RsaKey(), data);
}

/// <summary>
/// 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
/// </summary>
/// <param name="keyFile"></param>
/// <exception cref="ArgumentNullException"></exception>
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";
}
}
}
56 changes: 39 additions & 17 deletions src/VsLinuxDebugger/Core/SshTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,22 @@ public async Task CleanFolderAsync(string path)
public async Task<bool> ConnectAsync()
{
PrivateKeyFile keyFile = null;

ConnectionInfo conn = null;
try
{
if (_info.PrivateKeyEnabled)
{
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)
Expand All @@ -175,10 +182,15 @@ public async Task<bool> 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());
}
Expand All @@ -189,19 +201,29 @@ public async Task<bool> 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();
}
Expand Down
6 changes: 4 additions & 2 deletions src/VsLinuxDebugger/Core/UserOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class UserOptions
/// <summary>Base path to VSDBG (i.e. `~/.vsdbg`).</summary>
public string RemoteVsDbgBasePath { get; set; }
/// <summary>Full path to VS Debugger.</summary>
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; }
Expand All @@ -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;
}
}
4 changes: 2 additions & 2 deletions src/VsLinuxDebugger/OptionsPages/OptionsPage.DotNet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
Loading

0 comments on commit e4301ce

Please sign in to comment.