Skip to content

Commit

Permalink
Merge pull request #187 from FrendsPlatform/issue-181
Browse files Browse the repository at this point in the history
WriteFile - Updated Renci.SshNet library
  • Loading branch information
Svenskapojkarna authored Jan 3, 2024
2 parents 3b26f7f + fa908fb commit 7aea883
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 97 deletions.
8 changes: 8 additions & 0 deletions Frends.SFTP.WriteFile/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [2.2.0] - 2024-01-03
### Updated
- [Breaking] Updated dependency SSH.NET to the newest version 2023.0.0.

### Changed
- Changed connection info builder to create the connection info as it's done in DownloadFiles.
- [Breaking] Changed PrivateKeyFilePassphrase parameter to PrivateKeyPassphrase and enabled it when PrivateKeyString was used.

## [2.0.1] - 2022-12-01
### Updated
- Updated dependency Microsoft.Extensions.DependencyInjection to the newest version.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void WriteFile_TestWithLargerBuffer()
public void WriteFile_TestWithPrivateKeyFileRsa()
{
_connection.Authentication = AuthenticationType.UsernamePasswordPrivateKeyFile;
_connection.PrivateKeyFilePassphrase = "passphrase";
_connection.PrivateKeyPassphrase = "passphrase";
_connection.PrivateKeyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../Volumes/ssh_host_rsa_key");

SFTP.WriteFile(_input, _connection);
Expand All @@ -36,7 +36,7 @@ public void WriteFile_TestWithPrivateKeyFileRsaFromString()
var key = File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../Volumes/ssh_host_rsa_key"));

_connection.Authentication = AuthenticationType.UsernamePasswordPrivateKeyString;
_connection.PrivateKeyFilePassphrase = "passphrase";
_connection.PrivateKeyPassphrase = "passphrase";
_connection.PrivateKeyString = key;

SFTP.WriteFile(_input, _connection);
Expand Down
12 changes: 6 additions & 6 deletions Frends.SFTP.WriteFile/Frends.SFTP.WriteFile.Tests/ErrorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,22 @@ public void WriteFile_TestThrowsWithIncorrectCredentials()
public void WriteFile_TestThrowsWithIncorrectPrivateKeyPassphrase()
{
_connection.Authentication = AuthenticationType.UsernamePasswordPrivateKeyFile;
_connection.PrivateKeyFilePassphrase = "demo";
_connection.PrivateKeyPassphrase = "demo";
_connection.PrivateKeyFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "../../../Volumes/ssh_host_rsa_key");

var ex = Assert.Throws<ArgumentException>(() => SFTP.WriteFile(_input, _connection));
Assert.That(ex.Message.StartsWith("Error when initializing connection info:"));
Assert.IsTrue(ex.Message.StartsWith("Error when initializing connection info:"));
}

[Test]
public void WriteFile_TestThrowsWithEmptyPrivateKeyFile()
{
_connection.Authentication = AuthenticationType.UsernamePasswordPrivateKeyFile;
_connection.PrivateKeyFilePassphrase = "passphrase";
_connection.PrivateKeyPassphrase = "passphrase";
_connection.PrivateKeyFile = "";

var ex = Assert.Throws<ArgumentException>(() => SFTP.WriteFile(_input, _connection));
Assert.That(ex.Message.StartsWith("Error when initializing connection info: "));
Assert.IsTrue(ex.Message.StartsWith("Error when initializing connection info: "));
}

[Test]
Expand All @@ -65,11 +65,11 @@ public void WriteFile_TestThrowsWithIncorrectPrivateKeyString()
var key = Helpers.GenerateDummySshKey();

_connection.Authentication = AuthenticationType.UsernamePasswordPrivateKeyString;
_connection.PrivateKeyFilePassphrase = "passphrase";
_connection.PrivateKeyPassphrase = "passphrase";
_connection.PrivateKeyString = key.ToString();

var ex = Assert.Throws<ArgumentException>(() => SFTP.WriteFile(_input, _connection));
Assert.That(ex.Message.StartsWith("Error when initializing connection info: "));
Assert.IsTrue(ex.Message.StartsWith("Error when initializing connection info: "));
}

[Test]
Expand Down
26 changes: 5 additions & 21 deletions Frends.SFTP.WriteFile/Frends.SFTP.WriteFile.Tests/Lib/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ internal static Connection GetSftpConnection()
return connection;
}

internal static Tuple<byte[], byte[]> GetServerFingerPrintAndHostKey()
internal static Tuple<string, string, byte[]> GetServerFingerPrintsAndHostKey()
{
Tuple<byte[], byte[]> result = null;
Tuple<string, string, byte[]> result = null;
using (var client = new SftpClient(_dockerAddress, 2222, _dockerUsername, _dockerPassword))
{
client.ConnectionInfo.HostKeyAlgorithms.Clear();
client.ConnectionInfo.HostKeyAlgorithms.Add("ssh-rsa", (data) => { return new KeyHostAlgorithm("ssh-rsa", new RsaKey(), data); });

client.HostKeyReceived += delegate (object sender, HostKeyEventArgs e)
{
result = new Tuple<byte[], byte[]>(e.FingerPrint, e.HostKey);
result = new Tuple<string, string, byte[]>(e.FingerPrintMD5, e.FingerPrintSHA256, e.HostKey);
e.CanTrust = true;
};
client.Connect();
Expand All @@ -54,25 +54,10 @@ internal static Tuple<byte[], byte[]> GetServerFingerPrintAndHostKey()
return result;
}

internal static string ConvertToMD5Hex(byte[] fingerPrint)
{
return BitConverter.ToString(fingerPrint).Replace("-", ":");
}

internal static string ConvertToSHA256Hash(byte[] hostKey)
{
var fingerprint = "";
using (SHA256 mySHA256 = SHA256.Create())
{
fingerprint = Convert.ToBase64String(mySHA256.ComputeHash(hostKey));
}
return fingerprint;
}

internal static string ConvertToSHA256Hex(byte[] hostKey)
{
var fingerprint = "";
using (SHA256 mySHA256 = SHA256.Create())
using (var mySHA256 = SHA256.Create())
{
fingerprint = ToHex(mySHA256.ComputeHash(hostKey));
}
Expand All @@ -81,7 +66,7 @@ internal static string ConvertToSHA256Hex(byte[] hostKey)

internal static string ToHex(byte[] bytes)
{
StringBuilder result = new StringBuilder(bytes.Length * 2);
var result = new StringBuilder(bytes.Length * 2);
for (int i = 0; i < bytes.Length; i++)
result.Append(bytes[i].ToString("x2"));
return result.ToString();
Expand Down Expand Up @@ -130,7 +115,6 @@ internal static void DeleteDestinationFiles()
internal static SshKeyGenerator.SshKeyGenerator GenerateDummySshKey()
{
var keyBits = 2048;

return new SshKeyGenerator.SshKeyGenerator(keyBits);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public void SetUp()

[TearDown]
public void TearDown()
{
{
Helpers.DeleteDestinationFiles();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public class ServerFingerprintTests : WriteFileTestBase
[OneTimeSetUp]
public void OneTimeSetup()
{
var (fingerPrint, hostKey) = Helpers.GetServerFingerPrintAndHostKey();
_MD5 = Helpers.ConvertToMD5Hex(fingerPrint);
var (MD5, SHA256, hostKey) = Helpers.GetServerFingerPrintsAndHostKey();
_MD5 = MD5;
_Sha256Hex = Helpers.ConvertToSHA256Hex(hostKey);
_Sha256Hash = Helpers.ConvertToSHA256Hash(hostKey);
_Sha256Hash = SHA256;
}

[Test]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ public class Connection
/// Passphrase for the private key file.
/// </summary>
/// <example>passphrase</example>
[UIHint(nameof(Authentication), "", AuthenticationType.UsernamePrivateKeyFile, AuthenticationType.UsernamePasswordPrivateKeyFile)]
[UIHint(nameof(Authentication), "", AuthenticationType.UsernamePrivateKeyFile, AuthenticationType.UsernamePasswordPrivateKeyFile, AuthenticationType.UsernamePrivateKeyString, AuthenticationType.UsernamePasswordPrivateKeyString)]
[PasswordPropertyText]
public string PrivateKeyFilePassphrase { get; set; }
public string PrivateKeyPassphrase { get; set; }

/// <summary>
/// Fingerprint of the SFTP server. When using "Username-Password"
Expand Down Expand Up @@ -121,6 +121,12 @@ public class Connection
[DefaultValue(false)]
public bool UseKeyboardInteractiveAuthentication { get; set; }

/// <summary>
/// Responses for the server prompts when using Keyboard Interactive authentication method.
/// </summary>
[UIHint(nameof(UseKeyboardInteractiveAuthentication), "", true)]
public PromptResponse[] PromptAndResponse { get; set; } = Array.Empty<PromptResponse>();

/// <summary>
/// Integer value of used buffer size as KB.
/// Default value is 32 KB.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,33 +19,40 @@ internal ConnectionInfoBuilder(Input input, Connection connect)
internal ConnectionInfo BuildConnectionInfo()
{
ConnectionInfo connectionInfo;
List<AuthenticationMethod> methods = new List<AuthenticationMethod>();
var methods = new List<AuthenticationMethod>();

if (_connection.UseKeyboardInteractiveAuthentication)
{
// Construct keyboard-interactive authentication method
var kauth = new KeyboardInteractiveAuthenticationMethod(_connection.Username);
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(HandleKeyEvent);
methods.Add(kauth);
try
{
// Construct keyboard-interactive authentication method
var kauth = new KeyboardInteractiveAuthenticationMethod(_connection.Username);
kauth.AuthenticationPrompt += new EventHandler<AuthenticationPromptEventArgs>(HandleKeyEvent);
methods.Add(kauth);
}
catch (Exception ex)
{
throw new ArgumentException($"Failure in Keyboard-Interactive authentication: {ex.Message}");
}
}

PrivateKeyFile privateKey = null;
if (_connection.Authentication == AuthenticationType.UsernamePrivateKeyFile || _connection.Authentication == AuthenticationType.UsernamePasswordPrivateKeyFile)
{
if (string.IsNullOrEmpty(_connection.PrivateKeyFile))
throw new ArgumentException("Private key file path was not given.");
privateKey = (_connection.PrivateKeyFilePassphrase != null)
? new PrivateKeyFile(_connection.PrivateKeyFile, _connection.PrivateKeyFilePassphrase)
privateKey = (_connection.PrivateKeyPassphrase != null)
? new PrivateKeyFile(_connection.PrivateKeyFile, _connection.PrivateKeyPassphrase)
: new PrivateKeyFile(_connection.PrivateKeyFile);
}
if (_connection.Authentication == AuthenticationType.UsernamePrivateKeyString || _connection.Authentication == AuthenticationType.UsernamePasswordPrivateKeyString)
{
if (string.IsNullOrEmpty(_connection.PrivateKeyString))
throw new ArgumentException("Private key string was not given.");
var stream = new MemoryStream(Encoding.UTF8.GetBytes(_connection.PrivateKeyString));
privateKey = (_connection.PrivateKeyFilePassphrase != null)
? new PrivateKeyFile(stream, _connection.PrivateKeyFilePassphrase)
: new PrivateKeyFile(stream, "");
privateKey = (_connection.PrivateKeyPassphrase != null)
? new PrivateKeyFile(stream, _connection.PrivateKeyPassphrase)
: new PrivateKeyFile(stream);
}
switch (_connection.Authentication)
{
Expand All @@ -70,17 +77,40 @@ internal ConnectionInfo BuildConnectionInfo()
throw new ArgumentException($"Unknown Authentication type: '{_connection.Authentication}'.");
}

connectionInfo = new ConnectionInfo(_connection.Address, _connection.Port, _connection.Username, methods.ToArray());
connectionInfo.Encoding = Util.GetEncoding(_input.FileEncoding, _input.EnableBom, _input.EncodingInString);
connectionInfo = new ConnectionInfo(_connection.Address, _connection.Port, _connection.Username, methods.ToArray())
{
Encoding = Util.GetEncoding(_input.FileEncoding, _input.EnableBom, _input.EncodingInString),
ChannelCloseTimeout = TimeSpan.FromSeconds(_connection.ConnectionTimeout),
Timeout = TimeSpan.FromSeconds(_connection.ConnectionTimeout)
};

return connectionInfo;
}

private void HandleKeyEvent(object sender, AuthenticationPromptEventArgs e)
{
foreach (var prompt in e.Prompts)
if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
prompt.Response = _connection.Password;
if (e.Prompts.Any())
{
foreach (var serverPrompt in e.Prompts)
{
if (!string.IsNullOrEmpty(_connection.Password) && serverPrompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
serverPrompt.Response = _connection.Password;
else
{
if (!_connection.PromptAndResponse.Any() || !_connection.PromptAndResponse.Select(p => p.Prompt.ToLower()).ToList().Contains(serverPrompt.Request.Replace(":", "").Trim().ToLower()))
{
var errorMsg = $"Failure in Keyboard-interactive authentication: No response given for server prompt request --> {serverPrompt.Request.Replace(":", "").Trim()}";
throw new ArgumentException(errorMsg);
}

foreach (var prompt in _connection.PromptAndResponse)
{
if (serverPrompt.Request.IndexOf(prompt.Prompt, StringComparison.InvariantCultureIgnoreCase) != -1)
serverPrompt.Response = prompt.Response;
}
}
}
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

namespace Frends.SFTP.WriteFile.Definitions
{
/// <summary>
/// Prompt response class for Keyboard-interactive authentication.
/// </summary>
public class PromptResponse
{
/// <summary>
/// Prompt from the server what is to be expected.
/// </summary>
/// <example>Verification code</example>
public string Prompt { get; set; }

/// <summary>
/// Response for the Prompt from the server.
/// </summary>
/// <example>123456789</example>
[PasswordPropertyText]
[DisplayFormat(DataFormatString = "Text")]
public string Response { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ public class Result
/// Full path to the written file.
/// </summary>
/// <example>/destination/newfile.txt</example>
[DisplayFormat(DataFormatString = "Text")]
public string Path { get; private set; }
[DisplayFormat(DataFormatString = "Text")]
public string Path { get; private set; }

/// <summary>
/// Size of the new file in destination.
/// </summary>
/// <example>3.2</example>
public double SizeInMegaBytes { get; private set; }

internal Result(SftpFile file)
internal Result(ISftpFile file)
{
Path = file.FullName;
SizeInMegaBytes = Math.Round((file.Length / 1024d / 1024d), 3);
Expand Down
Loading

0 comments on commit 7aea883

Please sign in to comment.