diff --git a/Parallel.Cli/Commands/ConfigCommand.cs b/Parallel.Cli/Commands/ConfigCommand.cs index 6a42351..661a68d 100644 --- a/Parallel.Cli/Commands/ConfigCommand.cs +++ b/Parallel.Cli/Commands/ConfigCommand.cs @@ -11,6 +11,8 @@ namespace Parallel.Cli.Commands { public class ConfigCommand : Command { + private Option configOpt = new(["--config", "-c"], "The profile configuration to use."); + private Command addCmd = new("add", "Adds a new profile configuration."); private Command editCmd = new("edit", "Edits a profile configuration."); private Command viewCmd = new("view", "Shows the profile configuration."); diff --git a/Parallel.Cli/Commands/DecryptCommand.cs b/Parallel.Cli/Commands/DecryptCommand.cs index e031679..3e7d8ed 100644 --- a/Parallel.Cli/Commands/DecryptCommand.cs +++ b/Parallel.Cli/Commands/DecryptCommand.cs @@ -1,14 +1,84 @@ // Copyright 2025 Kyle Ebbinga using System.CommandLine; +using Parallel.Cli.Utils; +using Parallel.Core.Database; +using Parallel.Core.IO; +using Parallel.Core.Models; +using Parallel.Core.Settings; +using Parallel.Core.Utils; namespace Parallel.Cli.Commands { public class DecryptCommand : Command { + private readonly Argument _sourceArg = new("path", "The source path of files to zip."); + private readonly Option _configOpt = new(["--config", "-c"], "The profile configuration to use."); + + private IDatabase? _database; + public DecryptCommand() : base("decrypt", "Decrypts a file or directory.") { + this.AddArgument(_sourceArg); + this.SetHandler(async (path, config) => + { + ProfileConfig? profile = ProfileConfig.Load(Program.Settings, config); + if (profile == null) + { + CommandLine.WriteLine("No active profile was found!", ConsoleColor.Yellow); + return; + } + + _database = DatabaseConnection.CreateNew(profile); + string masterKey = profile.FileSystem.EncryptionKey ?? throw new ArgumentException("No encryption key provided!"); + if (PathBuilder.IsDirectory(path)) + { + await DecryptDirectoryAsync(path, masterKey); + } + else if (PathBuilder.IsFile(path)) + { + CommandLine.WriteLine($"Decrypting {path}...", ConsoleColor.DarkGray); + await DecryptFileAsync(path, masterKey); + + CommandLine.WriteLine($"Successfully decrypted file: {path}", ConsoleColor.Green); + } + else + { + CommandLine.WriteLine("The specified path is invalid.", ConsoleColor.Red); + } + }, _sourceArg, _configOpt); + } + + private Task DecryptDirectoryAsync(string path, string masterKey) + { + CommandLine.WriteLine($"Scanning for files in {path}...", ConsoleColor.DarkGray); + return Task.CompletedTask; + } + + private async Task DecryptFileAsync(string path, string masterKey) + { + SystemFile systemFile = await _database?.GetFileAsync(path)! ?? new SystemFile(path); + if (File.Exists(systemFile.LocalPath) && systemFile.Encrypted) + { + string tempFile = Path.Combine(PathBuilder.TempDirectory, Path.GetFileName(systemFile.LocalPath)) + ".tmp"; + CommandLine.WriteLine($"Writing to {tempFile}", ConsoleColor.DarkGray); + + await using (FileStream openFile = new FileStream(systemFile.LocalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + await using (FileStream createFile = new FileStream(tempFile, FileMode.OpenOrCreate)) + { + SystemFile result = Encryption.DecryptStream(openFile, createFile, systemFile, masterKey); + if (!await _database?.AddFileAsync(result)!) + { + CommandLine.WriteLine($"Failed to decrypt file: {systemFile.LocalPath}", ConsoleColor.Red); + if(File.Exists(tempFile)) File.Delete(tempFile); + } + } + + File.Copy(tempFile, systemFile.LocalPath, true); + //if(File.Exists(tempFile)) File.Delete(tempFile); + } + //CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw.Elapsed, ConsoleColor.DarkGray); } } } \ No newline at end of file diff --git a/Parallel.Cli/Commands/DuplicatesCommand.cs b/Parallel.Cli/Commands/DuplicatesCommand.cs new file mode 100644 index 0000000..4d5e2aa --- /dev/null +++ b/Parallel.Cli/Commands/DuplicatesCommand.cs @@ -0,0 +1,54 @@ +// Copyright 2025 Kyle Ebbinga + +using System.CommandLine; +using Parallel.Cli.Utils; +using Parallel.Core.IO.Backup; +using Parallel.Core.IO.Scanning; +using Parallel.Core.Models; +using Parallel.Core.Settings; + +using TextWriter = Parallel.Cli.Utils.TextWriter; + +namespace Parallel.Cli.Commands +{ + public class DuplicatesCommand : Command + { + private Argument sourceArg = new("path", "The directory to scan."); + private Option credsOpt = new(["--credentials", "-c"], "The file system credentials to use."); + + public DuplicatesCommand() : base("duplicates", "Scans a directory for duplicate files.") + { + this.AddArgument(sourceArg); + this.AddOption(credsOpt); + this.SetHandler((path, config) => + { + ProfileConfig profile = ProfileConfig.Load(Program.Settings, config); + ScanForDuplicateFiles(path, profile); + }, sourceArg, credsOpt); + } + + private void ScanForDuplicateFiles(string path, ProfileConfig profile) + { + IBackupManager backup = BackupManager.CreateNew(profile); + if (!backup.Initialize()) + { + CommandLine.WriteLine("Failed to connect to backup file system!", ConsoleColor.Red); + return; + } + + if (!Directory.Exists(path)) + { + CommandLine.WriteLine("The provided directory is invalid!", ConsoleColor.Yellow); + return; + } + + CommandLine.WriteLine($"Scanning for duplicate files in {path}...", ConsoleColor.DarkGray); + Dictionary duplicates = FileScanner.GetDuplicateFiles(path); + Dictionary result = duplicates.ToDictionary(k => k.Key, v => v.Value.Select(l => l.LocalPath).ToArray()); + long length = duplicates.Sum(kv => kv.Value.Sum(l => l.LocalSize)); + + CommandLine.WriteLine($"Scan found {duplicates.Where(kv => kv.Value.Length > 1).Count().ToString("N0")} duplicate files. ({Formatter.FromBytes(length)})"); + CommandLine.WriteLine($"A detailed version was created here: {TextWriter.CreateTxtFile(JsonConvert.SerializeObject(result, Formatting.Indented))}"); + } + } +} \ No newline at end of file diff --git a/Parallel.Cli/Commands/EncryptCommand.cs b/Parallel.Cli/Commands/EncryptCommand.cs index f4787b5..bd98de8 100644 --- a/Parallel.Cli/Commands/EncryptCommand.cs +++ b/Parallel.Cli/Commands/EncryptCommand.cs @@ -1,16 +1,86 @@ // Copyright 2025 Kyle Ebbinga using System.CommandLine; +using System.IO.Compression; +using Parallel.Cli.Utils; +using Parallel.Core.Database; +using Parallel.Core.IO; +using Parallel.Core.Models; +using Parallel.Core.Settings; +using Parallel.Core.Utils; namespace Parallel.Cli.Commands { public class EncryptCommand : Command { - private readonly Argument sourceArg = new("path", "The source path to encrypt."); + private readonly Argument _sourceArg = new("path", "The source path to encrypt."); + private readonly Option _configOpt = new(["--config", "-c"], "The profile configuration to use."); + + private IDatabase? _database; public EncryptCommand() : base("encrypt", "Encrypts a file or directory.") { + this.AddArgument(_sourceArg); + this.SetHandler(async (path, config) => + { + ProfileConfig? profile = ProfileConfig.Load(Program.Settings, config); + if (profile == null) + { + CommandLine.WriteLine("No active profile was found!", ConsoleColor.Yellow); + return; + } + + _database = DatabaseConnection.CreateNew(profile); + string masterKey = profile.FileSystem.EncryptionKey ?? throw new ArgumentException("No encryption key provided!"); + if (PathBuilder.IsDirectory(path)) + { + await EncryptDirectoryAsync(path, masterKey); + } + else if (PathBuilder.IsFile(path)) + { + CommandLine.WriteLine($"Encrypting {path}...", ConsoleColor.DarkGray); + await EncryptFileAsync(path, masterKey); + + CommandLine.WriteLine($"Successfully encrypted file: {path}", ConsoleColor.Green); + } + else + { + CommandLine.WriteLine("The specified path is invalid.", ConsoleColor.Red); + } + + }, _sourceArg, _configOpt); + } + + private Task EncryptDirectoryAsync(string path, string masterKey) + { + CommandLine.WriteLine($"Scanning for files in {path}...", ConsoleColor.DarkGray); + return Task.CompletedTask; + } + + private async Task EncryptFileAsync(string path, string masterKey) + { + SystemFile systemFile = await _database?.GetFileAsync(path)! ?? new SystemFile(path); + if (File.Exists(systemFile.LocalPath) && !systemFile.Encrypted) + { + string tempFile = Path.Combine(PathBuilder.TempDirectory, Path.GetFileName(systemFile.LocalPath)) + ".tmp"; + CommandLine.WriteLine($"Writing to {tempFile}", ConsoleColor.DarkGray); + + await using (FileStream openFile = new FileStream(systemFile.LocalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + await using (FileStream createFile = new FileStream(tempFile, FileMode.OpenOrCreate)) + { + SystemFile result = Encryption.EncryptStream(openFile, createFile, systemFile, masterKey); + if (!await _database?.AddFileAsync(result)!) + { + CommandLine.WriteLine($"Failed to decrypt file: {systemFile.LocalPath}", ConsoleColor.Red); + if(File.Exists(tempFile)) File.Delete(tempFile); + } + } + + File.Copy(tempFile, systemFile.LocalPath, true); + //if(File.Exists(tempFile)) File.Delete(tempFile); + } + //CommandLine.ProgressBar(_tasks.Count(t => t.IsCompleted), _totalTasks, _sw.Elapsed, ConsoleColor.DarkGray); } } } \ No newline at end of file diff --git a/Parallel.Core/Database/Contexts/SqliteContext.cs b/Parallel.Core/Database/Contexts/SqliteContext.cs index 3dd9c3e..738fab1 100644 --- a/Parallel.Core/Database/Contexts/SqliteContext.cs +++ b/Parallel.Core/Database/Contexts/SqliteContext.cs @@ -29,11 +29,13 @@ public SqliteContext(DatabaseCredentials credentials, string profileId) #region Base + /// public IDbConnection CreateConnection() { return new SqliteConnection("Data Source=" + FilePath); } + /// public async Task InitializeAsync() { Log.Information("Creating local database..."); @@ -41,20 +43,23 @@ public async Task InitializeAsync() File.SetAttributes(FilePath, File.GetAttributes(FilePath) | FileAttributes.Hidden); using IDbConnection connection = CreateConnection(); - await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `files` (`profile` TEXT NOT NULL, `id` TEXT NOT NULL, `name` TEXT NOT NULL, `path` TEXT NOT NULL, `lastwrite` LONG INTEGER NOT NULL, `lastupdate` LONG INTEGER NOT NULL, `localsize` LONG INTEGER NOT NULL, `remotesize` LONG INTEGER NOT NULL, `type` TEXT NOT NULL DEFAULT Other CHECK(`type` IN ('Document', 'Photo', 'Music', 'Video', 'Other')), `hidden` INTEGER NOT NULL DEFAULT 0, `readonly` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, `encrypted` INTEGER NOT NULL DEFAULT 0, `salt` BLOB NOT NULL, `iv` BLOB NOT NULL, PRIMARY KEY(`profile`, `id`));"); + await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `files` (`profile` TEXT NOT NULL, `id` TEXT NOT NULL, `name` TEXT NOT NULL, `localpath` TEXT NOT NULL, `remotepath` TEXT NOT NULL, `lastwrite` LONG INTEGER NOT NULL, `lastupdate` LONG INTEGER NOT NULL, `localsize` LONG INTEGER NOT NULL, `remotesize` LONG INTEGER NOT NULL, `type` TEXT NOT NULL DEFAULT Other CHECK(`type` IN ('Document', 'Photo', 'Music', 'Video', 'Other')), `hidden` INTEGER NOT NULL DEFAULT 0, `readonly` INTEGER NOT NULL DEFAULT 0, `deleted` INTEGER NOT NULL DEFAULT 0, `encrypted` INTEGER NOT NULL DEFAULT 0, `salt` BLOB, `iv` BLOB, PRIMARY KEY(`profile`, `id`));"); + await connection.ExecuteAsync("CREATE TABLE IF NOT EXISTS `history` (`profile` TEXT NOT NULL, `timestamp` LONG INTEGER NOT NULL, `path` TEXT NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, PRIMARY KEY(`profile`, `timestamp`));"); } #endregion #region Files + /// public async Task AddFileAsync(SystemFile file) { using IDbConnection connection = CreateConnection(); - string sql = @"INSERT OR REPLACE INTO files (profile, id, name, localpath, remotepath, lastwrite, lastupdate, LocalSize, RemoteSize, type, hidden, readonly, deleted) VALUES (@ProfileId, @Id, @Name, @LocalPath, @RemotePath, @LastWrite, @LastUpdate, @LocalSize, @RemoteSize, @Type, @Hidden, @ReadOnly, @Deleted);"; - return await connection.ExecuteAsync(sql, new { ProfileId, file.Id, file.Name, file.LocalPath, file.RemotePath, LastWrite = file.LastWrite.TotalMilliseconds, LastUpdate = file.LastUpdate.TotalMilliseconds, file.LocalSize, file.RemoteSize, }) > 0; + string sql = @"INSERT OR REPLACE INTO files (profile, id, name, localpath, remotepath, lastwrite, lastupdate, LocalSize, RemoteSize, type, hidden, readonly, deleted, encrypted, salt, iv) VALUES (@ProfileId, @Id, @Name, @LocalPath, @RemotePath, @LastWrite, @LastUpdate, @LocalSize, @RemoteSize, @Type, @Hidden, @ReadOnly, @Deleted, @Encrypted, @Salt, @IV);"; + return await connection.ExecuteAsync(sql, new { ProfileId, file.Id, file.Name, file.LocalPath, file.RemotePath, LastWrite = file.LastWrite.TotalMilliseconds, LastUpdate = UnixTime.Now.TotalMilliseconds, file.LocalSize, file.RemoteSize, Type = file.Type.ToString(), file.Hidden, file.ReadOnly, file.Deleted, file.Encrypted, file.Salt, file.IV }) > 0; } + /// public async Task> GetFilesAsync(string path, bool deleted) { using IDbConnection connection = CreateConnection(); @@ -62,10 +67,19 @@ public async Task> GetFilesAsync(string path, bool delet return await connection.QueryAsync(sql); } + /// + public async Task GetFileAsync(string path) + { + using IDbConnection connection = CreateConnection(); + string sql = $"SELECT * FROM files WHERE profile = \"{ProfileId}\" AND localpath LIKE \"%{path}%\" OR remotepath LIKE \"%{path}%\" ORDER BY lastupdate DESC"; + return await connection.QuerySingleOrDefaultAsync(sql); + } + #endregion #region History + /// public async Task AddHistoryAsync(string path, HistoryType type) { using IDbConnection connection = CreateConnection(); diff --git a/Parallel.Core/Database/IDatabase.cs b/Parallel.Core/Database/IDatabase.cs index 9583387..594674f 100644 --- a/Parallel.Core/Database/IDatabase.cs +++ b/Parallel.Core/Database/IDatabase.cs @@ -72,16 +72,28 @@ public interface IDatabase #region Files + /// + /// Adds a new file or updates an existing one. + /// + /// + /// True if successful, false otherwise Task AddFileAsync(SystemFile file); #endregion #region History + /// + /// Adds a new history. + /// + /// + /// + /// True if successful, false otherwise Task AddHistoryAsync(string path, HistoryType type); #endregion Task> GetFilesAsync(string path, bool b); + Task GetFileAsync(string path); } } \ No newline at end of file diff --git a/Parallel.Core/Events/LocalFileEventArgs.cs b/Parallel.Core/Events/LocalFileEventArgs.cs index 399768c..870d4d7 100644 --- a/Parallel.Core/Events/LocalFileEventArgs.cs +++ b/Parallel.Core/Events/LocalFileEventArgs.cs @@ -13,7 +13,7 @@ public class LocalFileEventArgs : EventArgs public LocalFileEventArgs(string file) { - SystemFile = new SystemFile(new FileInfo(file)); + SystemFile = new SystemFile(file); } } } \ No newline at end of file diff --git a/Parallel.Core/IO/Backup/BaseFileManager.cs b/Parallel.Core/IO/Backup/BaseFileManager.cs index 53f9b7e..45dc8d5 100644 --- a/Parallel.Core/IO/Backup/BaseFileManager.cs +++ b/Parallel.Core/IO/Backup/BaseFileManager.cs @@ -62,5 +62,11 @@ public virtual bool Initialize() /// public abstract Task RestoreFilesAsync(SystemFile[] files, IProgressReporter progress); + + /// + public abstract Task DecryptFilesAsync(SystemFile[] files, IProgressReporter progress); + + /// + public abstract Task EncryptFilesAsync(SystemFile[] files, IProgressReporter progress); } } \ No newline at end of file diff --git a/Parallel.Core/IO/Backup/DeltaBackupManager.cs b/Parallel.Core/IO/Backup/DeltaBackupManager.cs index 20c2084..3c1d8b6 100644 --- a/Parallel.Core/IO/Backup/DeltaBackupManager.cs +++ b/Parallel.Core/IO/Backup/DeltaBackupManager.cs @@ -28,5 +28,17 @@ public override Task RestoreFilesAsync(SystemFile[] files, IProgressReporter pro { throw new NotImplementedException(); } + + /// + public override Task DecryptFilesAsync(SystemFile[] files, IProgressReporter progress) + { + throw new NotImplementedException(); + } + + /// + public override Task EncryptFilesAsync(SystemFile[] files, IProgressReporter progress) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Parallel.Core/IO/Backup/FileBackupManager.cs b/Parallel.Core/IO/Backup/FileBackupManager.cs index 4cd4427..a134d4f 100644 --- a/Parallel.Core/IO/Backup/FileBackupManager.cs +++ b/Parallel.Core/IO/Backup/FileBackupManager.cs @@ -69,5 +69,17 @@ public override async Task RestoreFilesAsync(SystemFile[] files, IProgressReport file.RemotePath = PathBuilder.Remote(file.LocalPath, Profile.FileSystem); } } + + /// + public override Task DecryptFilesAsync(SystemFile[] files, IProgressReporter progress) + { + throw new NotImplementedException(); + } + + /// + public override Task EncryptFilesAsync(SystemFile[] files, IProgressReporter progress) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/Parallel.Core/IO/Backup/IBackupManager.cs b/Parallel.Core/IO/Backup/IBackupManager.cs index d819857..4022a7d 100644 --- a/Parallel.Core/IO/Backup/IBackupManager.cs +++ b/Parallel.Core/IO/Backup/IBackupManager.cs @@ -59,5 +59,14 @@ public interface IBackupManager /// /// Task RestoreFilesAsync(SystemFile[] files, IProgressReporter progress); + + /// + /// + /// + /// + /// + Task DecryptFilesAsync(SystemFile[] files, IProgressReporter progress); + + Task EncryptFilesAsync(SystemFile[] files, IProgressReporter progress); } } \ No newline at end of file diff --git a/Parallel.Core/IO/Scanning/FileScanner.cs b/Parallel.Core/IO/Scanning/FileScanner.cs index db1dbc7..90374fc 100644 --- a/Parallel.Core/IO/Scanning/FileScanner.cs +++ b/Parallel.Core/IO/Scanning/FileScanner.cs @@ -71,7 +71,7 @@ public async Task GetFileChangesAsync(string path, string[] ignore // Checks if a LocalFile exists on the current file system. if (File.Exists(rsf.LocalPath) && rsf.RemotePath != null) { - SystemFile lfi = new(new FileInfo(rsf.LocalPath)); + SystemFile lfi = new(rsf.LocalPath); if (IsIgnored(lfi.LocalPath, ignoreFolders)) { Log.Debug($"Is ignored -> {lfi.LocalPath}"); @@ -118,7 +118,7 @@ public async Task GetFileChangesAsync(string path, string[] ignore if (File.Exists(file) && !IsIgnored(file, ignoreFolders)) { Log.Debug($"Created -> {file}"); - scannedFiles.Add(new SystemFile(new FileInfo(file))); + scannedFiles.Add(new SystemFile(file)); localFiles.Remove(file); } } @@ -281,7 +281,7 @@ public static Dictionary GetDuplicateFiles(string path) IEnumerable files = GetFiles(path, "*"); foreach (string file in files) { - SystemFile entry = new(new FileInfo(file)); + SystemFile entry = new(file); if (dict.TryGetValue(entry.Name, out List value)) { SystemFile key = value.FirstOrDefault(); diff --git a/Parallel.Core/Models/SystemFile.cs b/Parallel.Core/Models/SystemFile.cs index 02a7ace..aa4ccdb 100644 --- a/Parallel.Core/Models/SystemFile.cs +++ b/Parallel.Core/Models/SystemFile.cs @@ -2,6 +2,7 @@ using System.Data; using Parallel.Core.Data; +using Parallel.Core.Diagnostics; using Parallel.Core.Utils; namespace Parallel.Core.Models @@ -92,16 +93,8 @@ public class SystemFile /// public SystemFile(string path) { + FileInfo fileInfo = new FileInfo(path); Id = HashGenerator.CreateSHA1(path); - } - - /// - /// Initializes a new instance of the class from a . - /// - /// - public SystemFile(FileInfo fileInfo) - { - Id = HashGenerator.CreateSHA1(fileInfo.FullName); Name = fileInfo.Name; LocalPath = fileInfo.FullName; LocalSize = fileInfo.Length; @@ -123,23 +116,26 @@ public SystemFile(FileInfo fileInfo) } /// - /// Initializes a new instance of the class from a . + /// Initializes a new instance of the class. /// /// - public SystemFile(DataRow row) + public SystemFile(string profile, string id, string name, string localpath, string remotepath, long lastwrite, long lastupdate, long localsize, long remotesize, string type, long hidden, long readOnly, long deleted, long encrypted, byte[] salt, byte[] iv) { - Id = row.Field("id"); - Name = row.Field("name"); - LocalPath = row.Field("localpath"); - RemotePath = row.Field("remotepath"); - LocalSize = Convert.ToInt64(row.Field("localsize")); - RemoteSize = Convert.ToInt64(row.Field("remotesize")); - LastWrite = UnixTime.FromMilliseconds(row.Field("lastwrite")); - LastUpdate = UnixTime.FromMilliseconds(row.Field("lastupdate")); - Type = (FileCategory)Enum.Parse(typeof(FileCategory), row.Field("type")); - Hidden = Converter.ToBool(Convert.ToInt32(row.Field("hidden"))); - ReadOnly = Converter.ToBool(Convert.ToInt32(row.Field("readonly"))); - Deleted = Converter.ToBool(Convert.ToInt32(row.Field("deleted"))); + Id = id; + Name = name; + LocalPath = localpath; + RemotePath = remotepath; + LastWrite = UnixTime.FromMilliseconds(lastwrite); + LastUpdate = UnixTime.FromMilliseconds(lastupdate); + LocalSize = localsize; + RemoteSize = remotesize; + //Type = type; + Hidden = Converter.ToBool(hidden); + ReadOnly = Converter.ToBool(readOnly); + Deleted = Converter.ToBool(deleted); + Encrypted = Converter.ToBool(encrypted); + Salt = salt; + IV = iv; } public bool Equals(SystemFile value) diff --git a/Parallel.Core/Parallel.Core.csproj b/Parallel.Core/Parallel.Core.csproj index 8ae9dd5..aa8cd3a 100644 --- a/Parallel.Core/Parallel.Core.csproj +++ b/Parallel.Core/Parallel.Core.csproj @@ -3,7 +3,7 @@ net9.0 enable - disable + enable 1.0.1.0 Entex Interactive, LLC Copyright Entex Interactive, LLC. All Rights Reserved. diff --git a/Parallel.Core/Security/Encryption.cs b/Parallel.Core/Security/Encryption.cs index 97a5db0..49820cb 100644 --- a/Parallel.Core/Security/Encryption.cs +++ b/Parallel.Core/Security/Encryption.cs @@ -2,6 +2,7 @@ using System.Security.Cryptography; using System.Text; +using Parallel.Core.Models; namespace Parallel.Core.Utils { @@ -40,49 +41,62 @@ public static string Decode(string value) /// /// Encrypts a . /// - /// The input stream. - /// The output stream. + /// + /// + /// /// - /// - public static void EncryptStream(Stream input, Stream output, string masterKey, UnixTime timestamp) + /// + public static SystemFile EncryptStream(Stream input, Stream output, SystemFile systemFile, string masterKey) { - byte[] salt = HashGenerator.RandomBytes(16); - byte[] iv = HashGenerator.RandomBytes(16); - byte[] derivedKey = HashGenerator.HKDF(masterKey, salt, timestamp.ToISOString(), 32); // 256-bit key + systemFile.Salt = HashGenerator.RandomBytes(16); + systemFile.IV = HashGenerator.RandomBytes(16); + systemFile.Encrypted = true; + input.Position = 0; + + Console.WriteLine($"Unencrypted stream length: {input.Length}"); + + byte[] derivedKey = HashGenerator.HKDF(masterKey, systemFile.Salt, systemFile.LastWrite.ToISOString(), 32); using (Aes aes = Aes.Create()) { aes.Key = derivedKey; - aes.IV = iv; + aes.IV = systemFile.IV; aes.Mode = CipherMode.CBC; using (CryptoStream cryptoStream = new CryptoStream(output, aes.CreateEncryptor(), CryptoStreamMode.Write)) { input.CopyTo(cryptoStream); } } + + return systemFile; } /// /// Decrypts a . /// - /// The input stream. - /// The output stream. + /// + /// + /// /// - /// - /// - /// - public static void DecryptStream(Stream input, Stream output, string masterKey, UnixTime timestamp, byte[] salt, byte[] iv) + public static SystemFile DecryptStream(Stream input, Stream output, SystemFile systemFile, string masterKey) { - byte[] derivedKey = HashGenerator.HKDF(masterKey, salt, timestamp.ToISOString(), 32); // 256-bit key + systemFile.Encrypted = false; + input.Position = 0; + + Console.WriteLine($"Encrypted stream length: {input.Length}"); + + byte[] derivedKey = HashGenerator.HKDF(masterKey, systemFile.Salt, systemFile.LastWrite.ToISOString(), 32); using (Aes aes = Aes.Create()) { aes.Key = derivedKey; - aes.IV = iv; + aes.IV = systemFile.IV; aes.Mode = CipherMode.CBC; using (CryptoStream cryptoStream = new CryptoStream(input, aes.CreateDecryptor(), CryptoStreamMode.Read)) { cryptoStream.CopyTo(output); } } + + return systemFile; } } } \ No newline at end of file diff --git a/Parallel.Core/Settings/ProfileConfig.cs b/Parallel.Core/Settings/ProfileConfig.cs index 93c2d17..5878534 100644 --- a/Parallel.Core/Settings/ProfileConfig.cs +++ b/Parallel.Core/Settings/ProfileConfig.cs @@ -112,10 +112,8 @@ public ProfileConfig(string name, DatabaseCredentials database, FileSystemCreden /// /// Loads settings from a file. /// - public static ProfileConfig Load(string name) + public static ProfileConfig Load(string path) { - ArgumentException.ThrowIfNullOrEmpty(name); - string path = Path.Combine(ParallelSettings.ProfilesDir, name + ".json"); if (File.Exists(path)) { string json = File.ReadAllText(path); @@ -123,6 +121,7 @@ public static ProfileConfig Load(string name) } else { + string name = Path.GetFileNameWithoutExtension(path); return new ProfileConfig(name, new DatabaseCredentials(), new FileSystemCredentials()); } } @@ -131,9 +130,9 @@ public static ProfileConfig Load(string name) /// Loads credentials from the app configuration. /// /// A instance. - public static ProfileConfig Load(ParallelSettings settings, string name) + public static ProfileConfig? Load(ParallelSettings settings, string name) { - ProfileConfig profile = Load(Path.GetFileNameWithoutExtension(settings.Profiles.FirstOrDefault())); + ProfileConfig profile = Load(settings.Profiles.First()); if (!string.IsNullOrEmpty(name)) { string path = Path.Combine(ParallelSettings.ProfilesDir, name + ".json"); diff --git a/Parallel.Core/Utils/Converter.cs b/Parallel.Core/Utils/Converter.cs index 4295054..8d55345 100644 --- a/Parallel.Core/Utils/Converter.cs +++ b/Parallel.Core/Utils/Converter.cs @@ -16,12 +16,7 @@ public static class Converter /// If 1 true, false otherwise. public static bool ToBool(double value) { - if (value.Equals(1)) - { - return true; - } - - return false; + return value.Equals(1); } /// @@ -31,6 +26,8 @@ public static bool ToBool(double value) /// If 1 true, false otherwise. public static int ToInt32(bool value) { + return value ? 1 : 0; + if (value) { return 1;