Compare commits

26 Commits

Author SHA1 Message Date
f81d09d540 Add EPSV command 2019-07-27 20:26:44 +02:00
351030ebcd Add ABOR command 2019-07-27 20:23:18 +02:00
bf42471fe5 Set process as daemon-like
Process was crashing when run as a Linux service
2019-07-22 14:10:23 +02:00
a28bff79fb Fix BypassGuestPassword
Was bypassing all users passwords and not only Guest
2019-07-22 11:19:42 +02:00
594e7eb1dd Add BypassGuestPassword configuration
Used to bypass the password for local connections
2019-07-21 21:28:28 +02:00
e8baee2d1e Move IsInternal() method to extension 2019-07-21 21:27:31 +02:00
2d517ea090 Change how PASV process local IP
Now use local ip for local connection
2019-07-21 21:15:02 +02:00
3f80eef90b Fix TLS negociations order 2019-07-21 20:05:57 +02:00
d77bff4531 Fix getting new file in NavigablePath
I should sleep more
2019-07-21 17:22:27 +02:00
eb0d9c8847 Add host configuration
Needed to fix remote access
2019-07-20 18:16:24 +02:00
e174f81fcc Fix a name typo 2019-07-20 17:51:08 +02:00
5a03dbf2a3 Add protection only when necessary 2019-07-20 17:50:55 +02:00
94431aebb8 Clean code
Remove unused usings
Remove blank lines
Fix style issues
2019-07-20 16:16:23 +02:00
ffb62b7737 Add certificate disposing on stop 2019-07-20 16:10:44 +02:00
f47100e940 Fix style issue 2019-07-20 16:08:06 +02:00
bbd83492a9 Add implicit FTPS 2019-07-20 16:05:08 +02:00
01fbd0b74e Add ForceDataPort configuration
Use StaticDataConnection
2019-07-20 14:31:33 +02:00
b2189e63df Remove unused code 2019-07-20 13:06:17 +02:00
23dfbe8788 Add SSL/TLS protocol support 2019-07-20 12:52:41 +02:00
c78181a1de Fix async client wait
Needed for REST
2019-07-18 14:22:43 +02:00
f39dd7b764 Add REST command 2019-07-18 14:22:11 +02:00
bac6728971 Add OPTS command 2019-07-18 13:18:27 +02:00
8c2b32d6b1 Add APPE and STOU command 2019-07-18 13:18:15 +02:00
a2f867480a Add timeout
Waiting client can't hang now
2019-07-18 13:01:55 +02:00
8ce87a3cfc Fix style issue
Now use deconstructor
2019-07-17 23:54:05 +02:00
5face5379c Fix NavigablePath
Copy was in reverse order
2019-07-17 23:53:49 +02:00
49 changed files with 1268 additions and 258 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System.Collections.Generic;
using DearFTP.Utils;
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using YamlDotNet.Serialization;
namespace DearFTP.Configurations
@@ -31,6 +32,7 @@ namespace DearFTP.Configurations
}
public ServerConfiguration Server { get; set; } = new ServerConfiguration();
public TlsConfiguration Tls { get; set; } = new TlsConfiguration();
public Share[] Shares { get; set; } = Array.Empty<Share>();
public User[] Users { get; set; } = Array.Empty<User>();
@@ -41,6 +43,16 @@ namespace DearFTP.Configurations
public bool Check()
{
if (string.IsNullOrWhiteSpace(Server.Host))
{
Console.WriteLine("Host address is missing.");
}
if (!IPAddress.TryParse(Server.Host, out var _))
{
Server.Host = Dns.GetHostEntry(Server.Host).AddressList.First().ToString();
}
foreach (var share in Shares)
{
share.Name = Path.GetFileName(share.Path);
@@ -58,12 +70,61 @@ namespace DearFTP.Configurations
return false;
}
if (Tls.ForceTls && !Tls.AllowTls)
{
Console.WriteLine("Tls is forced but not allowed.");
return false;
}
if (Server.ImplicitFtpsPort != 0 && !Tls.AllowTls)
{
Console.WriteLine("Implicit FTPS is activated but TLS is not allowed");
return false;
}
if (Tls.AllowTls)
{
if (string.IsNullOrWhiteSpace(Tls.CertificatePath))
{
Console.WriteLine("Tls is activated, but no certificate is specified.");
return false;
}
else
{
try
{
var certificate = new X509Certificate2(Tls.CertificatePath);
if (!certificate.HasPrivateKey)
{
if (string.IsNullOrWhiteSpace(Tls.PrivateKeyPath))
{
Console.WriteLine("No private key loaded and no path is specified.");
return false;
}
var privateKeyBytes = OpenSslKey.DecodePkcs8PrivateKey(File.ReadAllText(Tls.PrivateKeyPath));
var privateKey = OpenSslKey.DecodePrivateKeyInfo(privateKeyBytes);
certificate = certificate.CopyWithPrivateKey(privateKey);
}
Tls.X509Certificate = certificate;
}
catch (Exception e)
{
Console.WriteLine($"Can't load certificate: {e.Message}");
return false;
}
}
}
return true;
}
public void Save()
{
var serializer = new SerializerBuilder().EmitDefaults().Build();
var serializer = new SerializerBuilder().Build();
File.WriteAllText(ConfigurationPath, serializer.Serialize(this));
}

View File

@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Configurations
namespace DearFTP.Configurations
{
class ServerConfiguration
{
public string Host { get; set; } = "127.0.0.1";
public ushort Port { get; set; } = 21;
public ushort ForceDataPort { get; set; } = 0;
public ushort ImplicitFtpsPort { get; set; } = 0;
public string MOTD { get; set; } = "DearFTP v0.1";
public string LoginMessage { get; set; } = "Logged in as %user%";
@@ -14,5 +13,6 @@ namespace DearFTP.Configurations
public string LogFilePath { get; set; } = "log.txt";
public string Guest { get; set; } = "";
public bool BypassGuestPassword { get; set; } = false;
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization;
namespace DearFTP.Configurations
{

View File

@@ -0,0 +1,16 @@
using System.Security.Cryptography.X509Certificates;
using YamlDotNet.Serialization;
namespace DearFTP.Configurations
{
class TlsConfiguration
{
public bool AllowTls { get; set; } = false;
public bool ForceTls { get; set; } = false;
public string CertificatePath { get; set; } = "";
public string PrivateKeyPath { get; set; } = "";
[YamlIgnore()]
public X509Certificate2 X509Certificate { get; set; }
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Configurations
namespace DearFTP.Configurations
{
class User
{

View File

@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
{
class AbortCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"ABOR"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
session.DataConnection.Close();
stream.Send(ResponseCode.CloseDataConnection, "Data connection closed.");
}
}
}

View File

@@ -0,0 +1,34 @@
namespace DearFTP.Connection.Commands
{
class AuthCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"AUTH"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
string protocol = argument.ToUpper();
if (protocol != "TLS" && protocol != "TLS-C" && protocol != "SSL")
{
stream.Send(ResponseCode.ArgumentNotImplemented, "Invalid argument, expected 'TLS'");
return;
}
var tlsConfiguration = session.Configuration.Tls;
if (!tlsConfiguration.AllowTls)
{
session.LogError("Tls", "Client tried to use Tls but Tls is desactivated");
stream.Send(ResponseCode.NotImplemented, "Tls is not enabled on this server");
return;
}
stream.Send(ResponseCode.AcceptAuthenticationMechanism, "Tls activated.");
session.ActivateTls();
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class ClntCommand : ICommand
{

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -9,21 +7,28 @@ namespace DearFTP.Connection.Commands
{
public ICommand[] Commands { get; } = new ICommand[]
{
new AbortCommand(),
new AuthCommand(),
new ClntCommand(),
new CwdCommand(),
new DeleteCommand(),
new ExtendedPassiveCommand(),
new HelpCommand(),
new FeaturesCommand(),
new FileModificationTimeCommand(),
new ListCommand(),
new ListMachineCommand(),
new MakeDirectoryCommand(),
new OptionsCommand(),
new ParentDirectoryCommand(),
new PassiveCommand(),
new ProtectionBufferSizeCommand(),
new ProtectionCommand(),
new PwdCommand(),
new QuitCommand(),
new RenameCommand(),
new RetrieveCommand(),
new RestartCommand(),
new SiteCommand(),
new SizeCommand(),
new StoreCommand(),
@@ -41,15 +46,21 @@ namespace DearFTP.Connection.Commands
}
var commandExecutor = Commands.FirstOrDefault(x => x.Aliases.Contains(command, StringComparer.OrdinalIgnoreCase));
var stream = session.FtpStream;
if (commandExecutor == null)
{
session.FtpStream.Send(ResponseCode.NotImplemented, $"Command '{command}' not implemented or invalid");
stream.Send(ResponseCode.NotImplemented, $"Command '{command}' not implemented or invalid");
return;
}
commandExecutor.Execute(session, session.FtpStream, command, argument);
if (session.Configuration.Tls.ForceTls && !session.IsTlsProtected && command.ToUpper() != "AUTH")
{
stream.Send(ResponseCode.InsufficientProtection, "Not protected connection is not allowed on this server.");
return;
}
commandExecutor.Execute(session, stream, command, argument);
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class CwdCommand : ICommand
{

View File

@@ -1,10 +1,7 @@
using DearFTP.Configurations;
using DearFTP.Utils;
using DearFTP.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{
class ExtendedPassiveCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"EPSV"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
session.DataConnection.Create();
int port = session.DataConnection.Port;
stream.Send(ResponseCode.ExtendedPassiveMode, $"Entering Extended Passive Mode (|||{port}|)");
session.DataConnection.AcceptClient();
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class FeaturesCommand : ICommand
{
@@ -18,9 +14,12 @@ namespace DearFTP.Connection.Commands
(
ResponseCode.SystemStatusOrHelpReply,
"Features:",
"AUTH TLS",
"MDTM",
"MLST",
"PASV",
"PBSZ",
"PROT",
"REST STREAM",
"SIZE",
"TVFS",

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class HelpCommand : ICommand
{
@@ -22,9 +18,11 @@ namespace DearFTP.Connection.Commands
"ABOR",
"ALLO",
"APPE",
"AUTH",
"CDUP",
"CWD",
"DELE",
"EPSV",
"FEAT",
"HELP",
"LIST",
@@ -38,7 +36,9 @@ namespace DearFTP.Connection.Commands
"OPTS",
"PASS",
"PASV",
"PBSZ",
"PORT",
"PROT",
"PWD",
"QUIT",
"REIN",

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
interface ICommand
{

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Security;
using System.Text;
namespace DearFTP.Connection.Commands
@@ -31,6 +32,8 @@ namespace DearFTP.Connection.Commands
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
dataConnection.Authenticate();
string path = null;
bool humanReadable = false;

View File

@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Net.Security;
namespace DearFTP.Connection.Commands
{
@@ -72,6 +72,8 @@ namespace DearFTP.Connection.Commands
stream.Send(ResponseCode.FileStatusOK, "Listing coming.");
dataConnection.Authenticate();
var dataStream = new FtpStream(dataConnection.Stream);
foreach (var info in infos)
@@ -114,7 +116,7 @@ namespace DearFTP.Connection.Commands
{
string currentDirectory = navigablePath.GetDirectoryPath("");
yield return GenerateInfo(new DirectoryInfo(currentDirectory), writeable, "cdir");
yield return GenerateInfo(new DirectoryInfo(currentDirectory), writeable, "cdir");
if (virtualDirectory.Count(x => x == '/') > 1)
{

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -22,33 +20,33 @@ namespace DearFTP.Connection.Commands
return;
}
var newDirectory = session.NavigablePath.GetNewSystemFilePath(argument);
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(argument);
if (newDirectory.navigablePath.CurrentDirectory == "/")
if (navigablePath.CurrentDirectory == "/")
{
stream.Send(ResponseCode.FileUnavailable, "Can't modify the root.");
return;
}
if (newDirectory.realPath == null)
if (realPath == null)
{
stream.Send(ResponseCode.FileUnavailable, "Invalid destination path.");
return;
}
if (File.Exists(newDirectory.realPath) || Directory.Exists(newDirectory.realPath))
if (File.Exists(realPath) || Directory.Exists(realPath))
{
stream.Send(ResponseCode.FileUnavailable, "Destination path already exists.");
return;
}
if (!session.WritablesShares.Contains(newDirectory.navigablePath.CurrentShare))
if (!session.WritablesShares.Contains(navigablePath.CurrentShare))
{
stream.Send(ResponseCode.FileUnavailable, "You don't have write access to this file.");
return;
}
Directory.CreateDirectory(newDirectory.realPath);
Directory.CreateDirectory(realPath);
stream.Send(ResponseCode.FileActionOK, "Directory successfully created.");
}

View File

@@ -0,0 +1,21 @@
namespace DearFTP.Connection.Commands
{
class OptionsCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"OPTS"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
if (argument.ToUpper().Contains("UTF"))
{
stream.Send(ResponseCode.OK, "UTF8 supported on this server.");
return;
}
stream.Send(ResponseCode.ArgumentsError, "Requested option not supported");
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class ParentDirectoryCommand : ICommand
{

View File

@@ -1,9 +1,7 @@
using System;
using System.Collections.Generic;
using DearFTP.Utils.Extensions;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -20,7 +18,17 @@ namespace DearFTP.Connection.Commands
int port = session.DataConnection.Port;
var portBytes = BitConverter.GetBytes((ushort)port).Reverse().Select(x => x.ToString());
string remote = string.Join(',', session.IP.Split('.').Concat(portBytes));
string remote;
if (session.RemoteIP.IsInternal())
{
remote = string.Join(',', session.LocalIP.ToString().Split('.').Concat(portBytes));
}
else
{
remote = string.Join(',', session.Configuration.Server.Host.Split('.').Concat(portBytes));
}
stream.Send(ResponseCode.PassiveMode, $"Entering Passive Mode ({remote})");

View File

@@ -0,0 +1,21 @@
namespace DearFTP.Connection.Commands
{
class ProtectionBufferSizeCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"PBSZ"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
if (argument != "0")
{
stream.Send(ResponseCode.ArgumentsError, "Invalid argument, expected '0'");
return;
}
stream.Send(ResponseCode.OK, "Ok.");
}
}
}

View File

@@ -0,0 +1,36 @@
namespace DearFTP.Connection.Commands
{
class ProtectionCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"PROT"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
var dataConnection = session.DataConnection;
switch (argument.ToUpper())
{
case "C":
if (dataConnection.IsTlsProtected)
{
session.DataConnection.DesactivateTls();
}
stream.Send(ResponseCode.OK, "Data protection cleared.");
break;
case "P":
if (!dataConnection.IsTlsProtected)
{
session.DataConnection.ActivateTls();
}
stream.Send(ResponseCode.OK, "Data protection set.");
break;
default:
stream.Send(ResponseCode.ArgumentsError, "Invalid argument.");
break;
}
}
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class PwdCommand : ICommand
{

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class QuitCommand : ICommand
{

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{

View File

@@ -0,0 +1,23 @@
namespace DearFTP.Connection.Commands
{
class RestartCommand : ICommand
{
public string[] Aliases { get; } = new string[]
{
"REST"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
if (uint.TryParse(argument, out uint position))
{
session.RestartPosition = (int)position;
stream.Send(ResponseCode.PendingFurtherInformation, $"Restart position set to {position}.");
}
else
{
stream.Send(ResponseCode.ArgumentsError, "Specified position is not a valid number.");
}
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -18,7 +16,7 @@ namespace DearFTP.Connection.Commands
{
var dataConnection = session.DataConnection;
if (!dataConnection.IsAvailable)
if (!dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
{
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
return;
@@ -40,28 +38,52 @@ namespace DearFTP.Connection.Commands
stream.Send(ResponseCode.FileStatusOK, "File coming.");
SendFile(dataConnection.Stream, realPath);
dataConnection.Authenticate();
if (dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
{
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
return;
}
int restartPosition = session.RestartPosition;
session.RestartPosition = 0;
SendFile(dataConnection.Stream, realPath, restartPosition);
dataConnection.Close();
stream.Send(ResponseCode.CloseDataConnection, "File sent.");
}
private void SendFile(Stream stream, string path)
private void SendFile(Stream stream, string path, int offset)
{
using (var file = File.OpenRead(path))
{
if (offset != 0)
{
file.Seek(offset, SeekOrigin.Begin);
}
Span<byte> buffer = stackalloc byte[BufferSize];
long bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
while (file.Read(buffer) > 0)
{
if (bytesToWrite < BufferSize)
try
{
stream.Write(buffer.Slice(0, (int)bytesToWrite));
break;
if (bytesToWrite < BufferSize)
{
stream.Write(buffer.Slice(0, (int)bytesToWrite));
break;
}
stream.Write(buffer);
}
catch (Exception)
{
return;
}
stream.Write(buffer);
bytesToWrite = Math.Min(file.Length - file.Position, BufferSize);
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class SiteCommand : ICommand
{

View File

@@ -1,8 +1,4 @@
using DearFTP.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.IO;
namespace DearFTP.Connection.Commands
{

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -13,20 +10,24 @@ namespace DearFTP.Connection.Commands
public string[] Aliases { get; } = new string[]
{
"STOR"
"APPE",
"STOR",
"STOU"
};
public void Execute(Session session, FtpStream stream, string alias, string argument)
{
var dataConnection = session.DataConnection;
if (!dataConnection.IsAvailable)
if (!dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
{
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
return;
}
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(argument);
string path = alias.ToUpper() == "STOU" ? Path.GetRandomFileName() : argument;
var (navigablePath, realPath) = session.NavigablePath.GetNewSystemFilePath(path);
if (realPath == null)
{
@@ -48,16 +49,24 @@ namespace DearFTP.Connection.Commands
stream.Send(ResponseCode.FileStatusOK, "Waiting file.");
ReceiveFile(dataConnection.Stream, realPath);
dataConnection.Authenticate();
if (dataConnection.IsTlsProtected && !dataConnection.IsAvailable)
{
stream.Send(ResponseCode.DataConnectionOpenError, "Passive mode not activated.");
return;
}
ReceiveFile(dataConnection.Stream, realPath, alias.ToUpper() == "APPE");
dataConnection.Close();
stream.Send(ResponseCode.CloseDataConnection, "File received.");
}
private void ReceiveFile(NetworkStream stream, string path)
private void ReceiveFile(Stream stream, string path, bool append)
{
using (var file = File.Open(path, FileMode.Create))
using (var file = File.Open(path, append ? FileMode.Append : FileMode.Create))
{
Span<byte> buffer = stackalloc byte[BufferSize];
int readBytes;

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class SystemCommand : ICommand
{

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection.Commands
namespace DearFTP.Connection.Commands
{
class TypeCommand : ICommand
{

View File

@@ -1,8 +1,7 @@
using DearFTP.Utils;
using DearFTP.Utils.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DearFTP.Connection.Commands
{
@@ -17,10 +16,10 @@ namespace DearFTP.Connection.Commands
{
var configuration = FtpServer.Instance.Configuration;
var user = configuration.Users.FirstOrDefault(x => x.Name == argument);
string guest = configuration.Server.Guest;
if (user == null)
{
string guest = configuration.Server.Guest;
if (string.IsNullOrWhiteSpace(guest))
{
@@ -32,7 +31,7 @@ namespace DearFTP.Connection.Commands
user = configuration.Users.First(x => x.Name == guest);
}
if (!string.IsNullOrWhiteSpace(user.Password))
if (!string.IsNullOrWhiteSpace(user.Password) && !(session.RemoteIP.IsInternal() && configuration.Server.BypassGuestPassword && user.Name == guest))
{
stream.Send(ResponseCode.NeedPassword, "Please specify the password.");

View File

@@ -1,59 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace DearFTP.Connection
{
class DataConnection
{
public TcpListener Listener { get; private set; }
public TcpClient Client { get; private set; }
public NetworkStream Stream { get; private set; }
public int Port => ((IPEndPoint)Listener.LocalEndpoint).Port;
public bool IsAvailable { get; private set; }
public DataConnection()
{
IsAvailable = false;
}
public void Create()
{
// Clean old connections
if (Client?.Connected == true)
{
Close();
}
Listener = new TcpListener(IPAddress.Any, 0);
Listener.Start();
}
public void AcceptClient()
{
Client = Listener.AcceptTcpClient();
Stream = Client.GetStream();
IsAvailable = true;
}
public async void AcceptClientAsync()
{
Client = await Listener.AcceptTcpClientAsync();
Stream = Client.GetStream();
IsAvailable = true;
}
public void Close()
{
IsAvailable = false;
Stream.Close();
Client.Close();
Listener.Stop();
}
}
}

View File

@@ -0,0 +1,101 @@
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace DearFTP.Connection
{
class DynamicDataConnection : IDataConnection
{
public const int Timeout = 10_000;
public Stream Stream { get; private set; }
public bool IsTlsProtected { get; private set; }
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
public bool IsAvailable
{
get
{
if (_acceptTask == null)
{
return false;
}
else if (_acceptTask.Wait(Timeout))
{
return true;
}
else
{
_acceptTask.Dispose();
_acceptTask = null;
return false;
}
}
}
private TcpListener _listener;
private TcpClient _client;
private Task _acceptTask;
public DynamicDataConnection()
{
IsTlsProtected = false;
}
public void Create()
{
// Clean old connections
if (_client?.Connected == true)
{
Close();
}
_listener = new TcpListener(IPAddress.Any, 0);
_listener.Start();
}
public void AcceptClient()
{
_acceptTask = _listener.AcceptTcpClientAsync().ContinueWith(t =>
{
_client = t.Result;
if (IsTlsProtected)
{
Stream = new SslStream(_client.GetStream(), false);
}
else
{
Stream = _client.GetStream();
}
});
}
public void Authenticate()
{
if (IsTlsProtected)
{
((SslStream)Stream).AuthenticateAsServer(FtpServer.Instance.Configuration.Tls.X509Certificate, false, true);
}
}
public void ActivateTls()
{
IsTlsProtected = true;
}
public void DesactivateTls()
{
IsTlsProtected = false;
}
public void Close()
{
Stream.Close();
_client.Close();
_listener.Stop();
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
namespace DearFTP.Connection
@@ -10,18 +9,18 @@ namespace DearFTP.Connection
{
public const int BUFFER_SIZE = 4096;
public NetworkStream NetworkStream { get; }
public Stream Stream { get; }
public FtpStream(NetworkStream networkStream)
public FtpStream(Stream stream)
{
NetworkStream = networkStream;
Stream = stream;
}
public (string command, string argument) Receive()
{
var buffer = new byte[BUFFER_SIZE];
int readBytes = NetworkStream.Read(buffer, 0, buffer.Length);
int readBytes = Stream.Read(buffer, 0, buffer.Length);
if (readBytes == 0)
{
@@ -49,14 +48,14 @@ namespace DearFTP.Connection
{
var bytes = Encoding.UTF8.GetBytes($"{message}{(end ? "\r\n" : "")}");
NetworkStream.Write(bytes, 0, bytes.Length);
Stream.Write(bytes, 0, bytes.Length);
}
public void Send(ResponseCode code, string argument)
{
var bytes = Encoding.UTF8.GetBytes($"{(uint)code} {argument}\r\n");
NetworkStream.Write(bytes, 0, bytes.Length);
Stream.Write(bytes, 0, bytes.Length);
}
public void Send(ResponseCode code, string message, params string[] arguments)
@@ -74,7 +73,7 @@ namespace DearFTP.Connection
var bytes = Encoding.UTF8.GetBytes(builder.ToString());
NetworkStream.Write(bytes, 0, bytes.Length);
Stream.Write(bytes, 0, bytes.Length);
}
}
}

View File

@@ -0,0 +1,19 @@
using System.IO;
namespace DearFTP.Connection
{
interface IDataConnection
{
Stream Stream { get; }
int Port { get; }
bool IsTlsProtected { get; }
bool IsAvailable { get; }
void Create();
void AcceptClient();
void Authenticate();
void ActivateTls();
void DesactivateTls();
void Close();
}
}

View File

@@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace DearFTP.Connection
namespace DearFTP.Connection
{
public enum ResponseCode : uint
{
@@ -175,6 +171,10 @@ namespace DearFTP.Connection
/// </summary>
ArgumentNotImplemented = 504,
/// <summary>
/// Current level of protection is insufficient, need TLS/SSL
/// </summary>
InsufficientProtection = 522,
/// <summary>
/// Not logged in.
/// </summary>
NotLoggedIn = 530,

View File

@@ -2,12 +2,9 @@
using DearFTP.Connection.Commands;
using DearFTP.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
namespace DearFTP.Connection
{
@@ -19,14 +16,19 @@ namespace DearFTP.Connection
public User User { get; set; }
public Share[] Shares { get; set; }
public Share[] WritablesShares { get; set; }
public FtpStream FtpStream { get; }
public NavigablePath NavigablePath { get; set; }
public FtpStream FtpStream { get; private set; }
public IDataConnection DataConnection { get; set; }
public int RestartPosition { get; set; }
public bool IsTlsProtected { get; private set; }
public string CurrentWorkingDirectory => NavigablePath.CurrentDirectory;
public string IP => ((IPEndPoint)_client.Client.LocalEndPoint).Address.ToString();
public DataConnection DataConnection { get; set; }
private TcpClient _client;
private NetworkStream _networkStream;
public IPAddress LocalIP => ((IPEndPoint)_client.Client.LocalEndPoint).Address;
public IPAddress RemoteIP => ((IPEndPoint)_client.Client.RemoteEndPoint).Address;
private readonly TcpClient _client;
private readonly NetworkStream _networkStream;
private SslStream _sslStream;
private bool _isRunning = true;
public Session(TcpClient client)
@@ -38,7 +40,9 @@ namespace DearFTP.Connection
Configuration = FtpServer.Instance.Configuration;
CommandsDispatcher = FtpServer.Instance.CommandsDispatcher;
Logger = FtpServer.Instance.Logger;
DataConnection = new DataConnection();
DataConnection = Configuration.Server.ForceDataPort == 0 ? (IDataConnection)new DynamicDataConnection() : new StaticDataConnection();
RestartPosition = 0;
IsTlsProtected = false;
}
public void Start()
@@ -48,7 +52,7 @@ namespace DearFTP.Connection
while (_isRunning)
{
(string command, string argument) = FtpStream.Receive();
Logger.Log($"[{_client.Client.RemoteEndPoint}]: {command} {argument}");
Log($"{command} {argument}");
CommandsDispatcher.Dispatch(this, command, argument);
}
@@ -61,31 +65,33 @@ namespace DearFTP.Connection
Dispose();
}
public string GetRealPath(string path)
public void Log(string message)
{
string completePath = Path.Combine(CurrentWorkingDirectory, path);
Logger.Log($"[{_client.Client.RemoteEndPoint}]: {message}");
}
string[] split = CurrentWorkingDirectory.Substring(1).Split('/');
public void LogError(string error, string description)
{
Logger.LogError(error, $"[{_client.Client.RemoteEndPoint}]: {description}");
}
if (split.Length == 0)
{
return null;
}
public void ActivateTls()
{
_sslStream = new SslStream(_networkStream, true);
var share = Shares.FirstOrDefault(x => x.Name == split[0]);
_sslStream.AuthenticateAsServer(Configuration.Tls.X509Certificate, false, true);
if (share == null)
{
return null;
}
FtpStream = new FtpStream(_sslStream);
return Path.Combine(split.Skip(1).Prepend(share.Path).ToArray());
IsTlsProtected = true;
}
public void Dispose()
{
_sslStream?.Close();
_networkStream.Close();
_client.Close();
DataConnection.Close();
}
}
}

View File

@@ -0,0 +1,112 @@
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace DearFTP.Connection
{
class StaticDataConnection : IDataConnection
{
public const int Timeout = 10_000;
public Stream Stream { get; private set; }
public bool IsTlsProtected { get; private set; }
public int Port => ((IPEndPoint)_listener.LocalEndpoint).Port;
public bool IsAvailable
{
get
{
if (_acceptTask == null)
{
return false;
}
else if (_acceptTask.Wait(Timeout))
{
return true;
}
else
{
_acceptTask.Dispose();
_acceptTask = null;
return false;
}
}
}
private static TcpListener _listener;
private static Task _acceptTask;
private TcpClient _client;
public StaticDataConnection()
{
if (_listener == null)
{
_listener = new TcpListener(IPAddress.Any, FtpServer.Instance.Configuration.Server.ForceDataPort);
_listener.Start();
}
IsTlsProtected = false;
}
public void AcceptClient()
{
if (_acceptTask?.IsCompleted == false)
{
_acceptTask.Wait(Timeout);
_acceptTask.Dispose();
_acceptTask = null;
}
_acceptTask = _listener.AcceptTcpClientAsync().ContinueWith(t =>
{
_client = t.Result;
if (IsTlsProtected)
{
Stream = new SslStream(_client.GetStream(), false);
}
else
{
Stream = _client.GetStream();
}
});
}
public void Authenticate()
{
if (IsTlsProtected)
{
((SslStream)Stream).AuthenticateAsServer(FtpServer.Instance.Configuration.Tls.X509Certificate, false, true);
}
}
public void ActivateTls()
{
IsTlsProtected = true;
}
public void Close()
{
Stream.Close();
_client.Close();
}
public void Create()
{
// Nothing to create as the TcpListener is shared by all
}
public void DesactivateTls()
{
IsTlsProtected = false;
}
public static void Stop()
{
_listener?.Stop();
}
}
}

View File

@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using DearFTP.Configurations;
using DearFTP.Configurations;
using DearFTP.Connection;
using DearFTP.Connection.Commands;
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace DearFTP
{
@@ -21,6 +18,7 @@ namespace DearFTP
private bool _isRunning = true;
private TcpListener _listener;
private TcpListener _ftpsListener;
public FtpServer()
{
@@ -39,13 +37,21 @@ namespace DearFTP
Logger.Log("DearFTP started");
Logger.Log();
if (Configuration.Server.ImplicitFtpsPort != 0)
{
Task.Run(() => FtpsTcpLoop());
}
TcpLoop();
}
public void Stop()
{
_isRunning = false;
StaticDataConnection.Stop();
_listener.Stop();
_ftpsListener?.Stop();
Configuration.Tls.X509Certificate?.Dispose();
}
private void TcpLoop()
@@ -63,12 +69,33 @@ namespace DearFTP
}
}
private void CreateSession(TcpClient client)
private void FtpsTcpLoop()
{
_ftpsListener = new TcpListener(IPAddress.Any, Configuration.Server.ImplicitFtpsPort);
_ftpsListener.Start(5);
Logger.Log($"Listening on: {_ftpsListener.LocalEndpoint}");
while (_isRunning)
{
var client = _ftpsListener.AcceptTcpClient();
Task.Run(() => CreateSession(client, true));
}
}
private void CreateSession(TcpClient client, bool implicitFtps = false)
{
Logger.Log($"Client connected: {client.Client.RemoteEndPoint}");
using (var session = new Session(client))
{
if (implicitFtps)
{
session.ActivateTls();
session.DataConnection.ActivateTls();
}
session.Start();
}
}

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using DearFTP.Configurations;
using System;
using System.IO;
using System.Text;
using DearFTP.Configurations;
namespace DearFTP
{
@@ -10,12 +8,12 @@ namespace DearFTP
{
public bool WriteToFile { get; private set; }
public string FilePath { get; private set; }
public string FilePath { get; private set; }
public Logger(Configuration configuration)
{
WriteToFile = configuration.Server.LogFileEnabled;
FilePath = configuration.Server.LogFilePath;
FilePath = configuration.Server.LogFilePath;
}
public void Log(string message)

View File

@@ -2,8 +2,6 @@
using System;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace DearFTP
@@ -25,13 +23,7 @@ namespace DearFTP
var server = new FtpServer();
Task.Run(() => server.Start());
while (Console.ReadLine() != "stop") { }
server.Stop();
Environment.Exit(0);
server.Start();
}
private static void ProceedArguments(string[] args)

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
namespace DearFTP.Utils.Extensions
{
public static class IPAddressExtensions
{
// Source: https://stackoverflow.com/a/39120248
/// <summary>
/// An extension method to determine if an IP address is internal, as specified in RFC1918
/// </summary>
/// <param name="toTest">The IP address that will be tested</param>
/// <returns>Returns true if the IP is internal, false if it is external</returns>
public static bool IsInternal(this IPAddress toTest)
{
byte[] bytes = toTest.GetAddressBytes();
switch (bytes[0])
{
case 10:
return true;
case 172:
return bytes[1] < 32 && bytes[1] >= 16;
case 192:
return bytes[1] == 168;
default:
return false;
}
}
}
}

View File

@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace DearFTP.Utils
{
@@ -28,7 +27,7 @@ namespace DearFTP.Utils
CurrentShare = navigablePath.CurrentShare;
CurrentDirectory = navigablePath.CurrentDirectory;
_path = new Stack<string>(navigablePath._path);
_path = new Stack<string>(navigablePath._path.Reverse());
}
public string GetFilePath(string fileName)
@@ -138,7 +137,7 @@ namespace DearFTP.Utils
return (tempNavigablePath, null);
}
return (tempNavigablePath, Path.Combine(GetRealPath(), split.Last()));
return (tempNavigablePath, Path.Combine(tempNavigablePath.GetRealPath(), split.Last()));
}
public IEnumerable<string> GetDirectories()
@@ -193,7 +192,7 @@ namespace DearFTP.Utils
public bool NavigateTo(string path)
{
var oldPath = new Stack<string>(_path);
var oldPath = new Stack<string>(_path.Reverse());
var queue = new Queue<string>(path.Split('/'));
// If absolute path
@@ -205,7 +204,7 @@ namespace DearFTP.Utils
if (NavigateTo(queue))
{
CurrentDirectory = $"/{string.Join('/', _path)}";
CurrentDirectory = $"/{string.Join('/', _path.Reverse())}";
return true;
}
@@ -269,7 +268,7 @@ namespace DearFTP.Utils
return null;
}
return Path.Combine(_path.Skip(1).Prepend(CurrentShare.Path).ToArray());
return Path.Combine(_path.Reverse().Skip(1).Prepend(CurrentShare.Path).ToArray());
}
}
}

530
DearFTP/Utils/OpenSslKey.cs Normal file
View File

@@ -0,0 +1,530 @@
//**********************************************************************************
//
//OpenSSLKey
// .NET 2.0 OpenSSL Public & Private Key Parser
//
// Copyright (C) 2008 JavaScience Consulting
//
//***********************************************************************************
//
// opensslkey.cs
//
// Reads and parses:
// (1) OpenSSL PEM or DER public keys
// (2) OpenSSL PEM or DER traditional SSLeay private keys (encrypted and unencrypted)
// (3) PKCS #8 PEM or DER encoded private keys (encrypted and unencrypted)
// Keys in PEM format must have headers/footers .
// Encrypted Private Key in SSLEay format not supported in DER
// Removes header/footer lines.
// For traditional SSLEAY PEM private keys, checks for encrypted format and
// uses PBE to extract 3DES key.
// For SSLEAY format, only supports encryption format: DES-EDE3-CBC
// For PKCS #8, only supports PKCS#5 v2.0 3des.
// Parses private and public key components and returns .NET RSA object.
// Creates dummy unsigned certificate linked to private keypair and
// optionally exports to pkcs #12
//
// See also:
// http://www.openssl.org/docs/crypto/pem.html#PEM_ENCRYPTION_FORMAT
//**************************************************************************************
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;
using System.Text;
namespace DearFTP.Utils
{
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_KEY_PROV_INFO
{
[MarshalAs(UnmanagedType.LPWStr)] public string pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)] public string pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
[StructLayout(LayoutKind.Sequential)]
public struct CERT_NAME_BLOB
{
public int cbData;
public IntPtr pbData;
}
public class OpenSslKey
{
//-------- Get the binary PKCS #8 PRIVATE key --------
public static byte[] DecodePkcs8PrivateKey(string instr)
{
const string pemp8header = "-----BEGIN PRIVATE KEY-----";
const string pemp8footer = "-----END PRIVATE KEY-----";
string pemstr = instr.Trim();
byte[] binkey;
if (!pemstr.StartsWith(pemp8header) || !pemstr.EndsWith(pemp8footer))
return null;
StringBuilder sb = new StringBuilder(pemstr);
sb.Replace(pemp8header, ""); //remove headers/footers, if present
sb.Replace(pemp8footer, "");
string pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try
{
binkey = Convert.FromBase64String(pubstr);
}
catch (FormatException)
{ //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
//------- Parses binary asn.1 PKCS #8 PrivateKeyInfo; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
byte[] seq;
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(pkcs8);
int lenstream = (int)mem.Length;
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt;
ushort twobytes;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if (bt != 0x02)
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0001)
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
bt = binr.ReadByte();
if (bt != 0x04) //expect an Octet string
return null;
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
if (bt == 0x81)
binr.ReadByte();
else
if (bt == 0x82)
binr.ReadUInt16();
//------ at this stage, the remaining sequence should be the RSA private key
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position));
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
return rsacsp;
}
catch (Exception)
{
return null;
}
finally
{
binr.Close();
}
}
//-------- Get the binary PKCS #8 Encrypted PRIVATE key --------
public static byte[] DecodePkcs8EncPrivateKey(string instr)
{
const string pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
const string pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----";
string pemstr = instr.Trim();
byte[] binkey;
if (!pemstr.StartsWith(pemp8encheader) || !pemstr.EndsWith(pemp8encfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr);
sb.Replace(pemp8encheader, ""); //remove headers/footers, if present
sb.Replace(pemp8encfooter, "");
string pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try
{
binkey = Convert.FromBase64String(pubstr);
}
catch (System.FormatException)
{ //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
//------- Parses binary asn.1 EncryptedPrivateKeyInfo; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodeEncryptedPrivateKeyInfo(byte[] encpkcs8)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] OIDpkcs5PBES2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D };
byte[] OIDpkcs5PBKDF2 = { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C };
byte[] OIDdesEDE3CBC = { 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07 };
byte[] seqdes;
byte[] seq;
byte[] salt;
byte[] IV;
byte[] encryptedpkcs8;
byte[] pkcs8;
int saltsize, ivsize, encblobsize;
int iterations;
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(encpkcs8);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt;
ushort twobytes;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16(); //inner sequence
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if (!CompareBytearrays(seq, OIDpkcs5PBES2)) //is it a OIDpkcs5PBES2 ?
return null;
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if (!CompareBytearrays(seq, OIDpkcs5PBKDF2)) //is it a OIDpkcs5PBKDF2 ?
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
bt = binr.ReadByte();
if (bt != 0x04) //expect octet string for salt
return null;
saltsize = binr.ReadByte();
salt = binr.ReadBytes(saltsize);
bt = binr.ReadByte();
if (bt != 0x02) //expect an integer for PBKF2 interation count
return null;
int itbytes = binr.ReadByte(); //PBKD2 iterations should fit in 2 bytes.
if (itbytes == 1)
iterations = binr.ReadByte();
else if (itbytes == 2)
iterations = 256 * binr.ReadByte() + binr.ReadByte();
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130)
binr.ReadByte();
else if (twobytes == 0x8230)
binr.ReadInt16();
seqdes = binr.ReadBytes(10); //read the Sequence OID
if (!CompareBytearrays(seqdes, OIDdesEDE3CBC)) //is it a OIDdes-EDE3-CBC ?
return null;
bt = binr.ReadByte();
if (bt != 0x04) //expect octet string for IV
return null;
ivsize = binr.ReadByte(); // IV byte size should fit in one byte (24 expected for 3DES)
IV = binr.ReadBytes(ivsize);
bt = binr.ReadByte();
if (bt != 0x04) // expect octet string for encrypted PKCS8 data
return null;
bt = binr.ReadByte();
if (bt == 0x81)
encblobsize = binr.ReadByte(); // data size in next byte
else if (bt == 0x82)
encblobsize = 256 * binr.ReadByte() + binr.ReadByte();
else
encblobsize = bt; // we already have the data size
encryptedpkcs8 = binr.ReadBytes(encblobsize);
SecureString secpswd = GetSecPswd("Enter password for Encrypted PKCS #8 ==>");
pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations);
if (pkcs8 == null) // probably a bad pswd entered.
return null;
//----- With a decrypted pkcs #8 PrivateKeyInfo blob, decode it to an RSA ---
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8);
return rsa;
}
catch (Exception)
{
return null;
}
finally
{
binr.Close();
}
}
// ------ Uses PBKD2 to derive a 3DES key and decrypts data --------
public static byte[] DecryptPBDK2(byte[] edata, byte[] salt, byte[] IV, SecureString secpswd, int iterations)
{
byte[] psbytes = new byte[secpswd.Length];
IntPtr unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length);
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
try
{
Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
TripleDES decAlg = TripleDES.Create();
decAlg.Key = kd.GetBytes(24);
decAlg.IV = IV;
MemoryStream memstr = new MemoryStream();
CryptoStream decrypt = new CryptoStream(memstr, decAlg.CreateDecryptor(), CryptoStreamMode.Write);
decrypt.Write(edata, 0, edata.Length);
decrypt.Flush();
decrypt.Close(); // this is REQUIRED.
byte[] cleartext = memstr.ToArray();
return cleartext;
}
catch (Exception e)
{
Console.WriteLine("Problem decrypting: {0}", e.Message);
return null;
}
}
//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey);
BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading
byte bt;
ushort twobytes;
int elems;
try
{
twobytes = binr.ReadUInt16();
if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if (twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if (twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if (bt != 0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems);
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters
{
Modulus = MODULUS,
Exponent = E,
D = D,
P = P,
Q = Q,
DP = DP,
DQ = DQ,
InverseQ = IQ
};
RSA.ImportParameters(RSAparams);
return RSA;
}
catch (Exception)
{
return null;
}
finally { binr.Close(); }
}
private static int GetIntegerSize(BinaryReader binr)
{
byte bt = binr.ReadByte();
if (bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
int count;
if (bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else if (bt == 0x82)
{
byte highbyte = binr.ReadByte();
byte lowbyte = binr.ReadByte();
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
count = BitConverter.ToInt32(modint, 0);
}
else
{
count = bt; // we already have the data size
}
while (binr.ReadByte() == 0x00)
{ //remove high order zeros in data
count -= 1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
private static SecureString GetSecPswd(string prompt)
{
SecureString password = new SecureString();
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(prompt);
Console.ForegroundColor = ConsoleColor.Magenta;
while (true)
{
ConsoleKeyInfo cki = Console.ReadKey(true);
if (cki.Key == ConsoleKey.Enter)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (cki.Key == ConsoleKey.Backspace)
{
// remove the last asterisk from the screen...
if (password.Length > 0)
{
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
password.RemoveAt(password.Length - 1);
}
}
else if (cki.Key == ConsoleKey.Escape)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (char.IsLetterOrDigit(cki.KeyChar) || char.IsSymbol(cki.KeyChar))
{
if (password.Length < 20)
{
password.AppendChar(cki.KeyChar);
Console.Write("*");
}
else
{
Console.Beep();
}
}
else
{
Console.Beep();
}
}
}
private static bool CompareBytearrays(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
int i = 0;
foreach (byte c in a)
{
if (c != b[i])
return false;
i++;
}
return true;
}
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Security.Cryptography;
using System.Text;