From ffdf925d258808f04e6c9c4857ea6625028df1f0 Mon Sep 17 00:00:00 2001 From: Artem Saikin Date: Thu, 12 May 2022 03:17:58 +0300 Subject: [PATCH 1/6] POC for encrypted support packages --- src/Orc.SupportPackage.Example/App.xaml.cs | 4 +- .../Services/SupportPackageBuilderService.cs | 88 ++----- .../Context/SupportPackageContext.cs | 20 +- .../Cryptography/EncryptionContext.cs | 45 ++++ .../Cryptography/EncryptionService.cs | 218 ++++++++++++++++++ .../Cryptography/Globals.cs | 8 + .../Interfaces/IEncryptionService.cs | 12 + .../Models/SupportPackageOptions.cs | 17 ++ src/Orc.SupportPackage/ModuleInitializer.cs | 3 +- .../Interfaces/ISupportPackageService.cs | 7 +- .../Services/SupportPackageService.cs | 192 ++++++++++++--- 11 files changed, 499 insertions(+), 115 deletions(-) create mode 100644 src/Orc.SupportPackage/Cryptography/EncryptionContext.cs create mode 100644 src/Orc.SupportPackage/Cryptography/EncryptionService.cs create mode 100644 src/Orc.SupportPackage/Cryptography/Globals.cs create mode 100644 src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs create mode 100644 src/Orc.SupportPackage/Models/SupportPackageOptions.cs diff --git a/src/Orc.SupportPackage.Example/App.xaml.cs b/src/Orc.SupportPackage.Example/App.xaml.cs index 78d62842..f7e1474b 100644 --- a/src/Orc.SupportPackage.Example/App.xaml.cs +++ b/src/Orc.SupportPackage.Example/App.xaml.cs @@ -35,7 +35,9 @@ protected override void OnStartup(StartupEventArgs e) this.ApplyTheme(); + Globals.EnableEncryption = true; + base.OnStartup(e); } } -} \ No newline at end of file +} diff --git a/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs b/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs index f0ed8c23..8cefe176 100644 --- a/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs +++ b/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs @@ -20,8 +20,6 @@ namespace Orc.SupportPackage public class SupportPackageBuilderService : ISupportPackageBuilderService { - private const int DirectorySizeLimitInBytes = 25 * 1024 * 1024; - #region Fields private static readonly ILog Log = LogManager.GetCurrentClassLogger(); @@ -61,7 +59,9 @@ public virtual async Task CreateSupportPackageAsync(string fileName, List< } var excludeFileNamePatterns = artifacts.Where(artifact => !artifact.IncludeInSupportPackage).OfType().SelectMany(artifact => artifact.FileNamePatterns).Distinct().ToArray(); - var directories = artifacts.Where(artifact => artifact.IncludeInSupportPackage).OfType().Select(artifact => artifact.DirectoryName).Distinct().ToArray(); + var directories = artifacts.Where(artifact => artifact.IncludeInSupportPackage).OfType().Select(artifact => artifact.DirectoryName) + .Distinct() + .ToArray(); builder.AppendLine(); builder.AppendLine("## Exclude file name patterns"); @@ -81,81 +81,25 @@ public virtual async Task CreateSupportPackageAsync(string fileName, List< builder.AppendLine("- " + directory); } - var result = await _supportPackageService.CreateSupportPackageAsync(fileName, directories, excludeFileNamePatterns); - - var customDataDirectoryName = "CustomData"; - using (var fileStream = new FileStream(fileName, FileMode.OpenOrCreate)) + var customData = artifacts.Where(artifact => artifact.IncludeInSupportPackage).OfType().SelectMany(x => x.Paths); + if (customData.Any()) { - using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Update)) - { - foreach (var artifact in artifacts.OfType().Where(artifact => artifact.IncludeInSupportPackage)) - { - builder.AppendLine(); - builder.AppendLine("## Include custom data"); - builder.AppendLine(); - - foreach (var path in artifact.Paths) - { - try - { - var directoryInfo = new DirectoryInfo(path); - if (directoryInfo.Exists) - { - var directorySize = directoryInfo.GetFiles("*.*", SearchOption.AllDirectories).Sum(info => info.Length); - if (directorySize > DirectorySizeLimitInBytes) - { - Log.Info("Skipped directory '{0}' beacuse its size is greater than '{1}' bytes", path, DirectorySizeLimitInBytes); - - builder.AppendLine("- Directory (skipped): " + path); - } - else - { - zipArchive.CreateEntryFromDirectory(path, Path.Combine(customDataDirectoryName, directoryInfo.Name), CompressionLevel.Optimal); - builder.AppendLine("- Directory: " + path); - } - } - } - catch (Exception ex) - { - Log.Warning(ex); - } - - try - { - if (_fileService.Exists(path)) - { - zipArchive.CreateEntryFromAny(path, customDataDirectoryName, CompressionLevel.Optimal); - builder.AppendLine("- File: " + path); - } - } - catch (Exception ex) - { - Log.Warning(ex); - } - } - } - - builder.AppendLine(); - builder.AppendLine("## File system entries"); - builder.AppendLine(); - builder.AppendLine("- Total: " + zipArchive.Entries.Count); - builder.AppendLine("- Files: " + zipArchive.Entries.Count(entry => !entry.Name.EndsWith("/"))); - builder.AppendLine("- Directories: " + zipArchive.Entries.Count(entry => entry.Name.EndsWith("/"))); - - var builderEntry = zipArchive.CreateEntry("SupportPackageOptions.txt"); - - using (var streamWriter = new StreamWriter(builderEntry.Open())) - { - await streamWriter.WriteAsync(builder.ToString()); - } - - await fileStream.FlushAsync(); - } + builder.AppendLine(); + builder.AppendLine("## Include custom data"); + builder.AppendLine(); } + var result = await _supportPackageService.CreateSupportPackageAsync(fileName, directories, excludeFileNamePatterns, new SupportPackageOptions + { + FileSystemPaths = customData.ToArray(), + DescriptionBuilder = builder + }); + return result; } + + #endregion } } diff --git a/src/Orc.SupportPackage/Context/SupportPackageContext.cs b/src/Orc.SupportPackage/Context/SupportPackageContext.cs index 53403307..14f2b37a 100644 --- a/src/Orc.SupportPackage/Context/SupportPackageContext.cs +++ b/src/Orc.SupportPackage/Context/SupportPackageContext.cs @@ -8,6 +8,7 @@ namespace Orc.SupportPackage { using System; + using System.Collections.Generic; using System.IO; using Catel; using Catel.Logging; @@ -19,6 +20,9 @@ public class SupportPackageContext : Disposable, ISupportPackageContext private readonly string _rootDirectory; + private readonly List _artifactsDirectories = new(); + private readonly List _excludefileNamePatterns = new(); + public SupportPackageContext() { var assembly = AssemblyHelper.GetEntryAssembly(); @@ -29,7 +33,11 @@ public SupportPackageContext() Directory.CreateDirectory(_rootDirectory); } - public string RootDirectory { get { return _rootDirectory; } } + public string RootDirectory => _rootDirectory; + + public IReadOnlyCollection ExcludeFileNamePatterns => _excludefileNamePatterns; + + public IReadOnlyCollection ArtifactsDirectories => _artifactsDirectories; public string GetDirectory(string relativeDirectoryName) { @@ -56,6 +64,16 @@ public string GetFile(string relativeFilePath) return fullPath; } + public void AddArtifactDirectories(string[] directories) + { + _artifactsDirectories.AddRange(directories); + } + + public void AddExcludeFileNamePatterns(string[] fileNamePatterns) + { + _excludefileNamePatterns.AddRange(fileNamePatterns); + } + protected override void DisposeManaged() { Log.Info("Deleting temporary files from '{0}'", _rootDirectory); diff --git a/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs b/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs new file mode 100644 index 00000000..0671febd --- /dev/null +++ b/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs @@ -0,0 +1,45 @@ +namespace Orc.SupportPackage +{ + using System; + using Catel; + + public class EncryptionContext + { + public EncryptionContext() + { + + } + + public EncryptionContext(EncryptionContext context) + { + Argument.IsNotNull(() => context); + + PassPhrase = context.PassPhrase; + SaltValue = context.SaltValue; + PasswordIterations = context.PasswordIterations; + InitVector = context.InitVector; + KeySize = context.KeySize; + } + + public EncryptionContext(string passPhrase, string saltValue, int? passwordIterations, string initVector, int? keySize) + { + PassPhrase = passPhrase; + SaltValue = saltValue; + PasswordIterations = passwordIterations; + InitVector = initVector; + KeySize = keySize; + } + + public string PassPhrase { get; set; } + + public string SaltValue { get; set; } + + public int? PasswordIterations { get; set; } + + public string InitVector { get; set; } + + public int? KeySize { get; set; } + + public IProgress Progress { get; set; } + } +} diff --git a/src/Orc.SupportPackage/Cryptography/EncryptionService.cs b/src/Orc.SupportPackage/Cryptography/EncryptionService.cs new file mode 100644 index 00000000..463c2ab2 --- /dev/null +++ b/src/Orc.SupportPackage/Cryptography/EncryptionService.cs @@ -0,0 +1,218 @@ +namespace Orc.SupportPackage +{ + using System; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using System.Threading.Tasks; + using Catel; + using Catel.Logging; + + public class EncryptionService : IEncryptionService + { + private static readonly ILog Log = LogManager.GetCurrentClassLogger(); + + private const int PasswordIterations = 2071; + private const int KeySize = 256; + + private const string SaltValue = "5Sr>3rCxUTf@3~Uu"; + private const string InitVector = "fe,r7 content.PassPhrase); + + // Convert strings into byte arrays. + // Let us assume that strings only contain ASCII codes. + // If strings include Unicode characters, use Unicode, UTF7, or UTF8 encoding. + var initVectorBytes = Encoding.UTF8.GetBytes(content.InitVector ?? InitVector); + var saltValueBytes = Encoding.UTF8.GetBytes(content.SaltValue ?? SaltValue); + + // First, we must create a password, from which the key will be derived. + // This password will be generated from the specified passphrase and + // salt value. The password will be created using the specified hash + // algorithm. Password creation can be done in several iterations. + using (var password = new Rfc2898DeriveBytes(content.PassPhrase, saltValueBytes, content.PasswordIterations ?? PasswordIterations)) + { + using (var hmac = new HMACSHA256(GetRawKey(content))) + { + // Use the password to generate pseudo-random bytes for the encryption + // key. Specify the size of the key in bytes (instead of bits). + var keyBytes = password.GetBytes(Convert.ToInt32((content.KeySize ?? KeySize) / 8)); + var hashBytesSize = hmac.HashSize / 8; + + using (var symmetricKey = CreateAlghorithm()) + { + // Generate encryptor from the existing key bytes and initialization + // vector. Key size will be defined based on the number of the key + // bytes. + using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)) + { + // Define cryptographic stream (always use Write mode for encryption) + using (var cryptoStream = new CryptoStream(targetStream, encryptor, CryptoStreamMode.Write, true)) + { + targetStream.Seek(hashBytesSize, SeekOrigin.Begin); + + await sourceStream.CopyToAsync(cryptoStream, 4096); + await cryptoStream.FlushAsync(); + + // Finish encrypting +#pragma warning disable CL0001 // Use async overload inside this async method + cryptoStream.FlushFinalBlock(); +#pragma warning restore CL0001 // Use async overload inside this async method + + await targetStream.FlushAsync(); + } + } + + targetStream.Seek(hashBytesSize, SeekOrigin.Begin); + var hmacbytes = hmac.ComputeHash(targetStream); + + targetStream.Seek(0, SeekOrigin.Begin); + targetStream.Write(hmacbytes, 0, hmacbytes.Length); // Always 32 bytes + + await targetStream.FlushAsync(); + } + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to encrypt"); + + throw; + } + } + + public async Task DecryptAsync( + Stream sourceStream, + Stream targetStream, + EncryptionContext content) + { + try + { + Argument.IsNotNullOrEmpty(() => content.PassPhrase); + + // Convert strings defining encryption key characteristics into byte + // arrays. Let us assume that strings only contain ASCII codes. + // If strings include Unicode characters, use Unicode, UTF7, or UTF8 + // encoding. + var initVectorBytes = Encoding.UTF8.GetBytes(content.InitVector ?? InitVector); + var saltValueBytes = Encoding.UTF8.GetBytes(content.SaltValue ?? SaltValue); + + var canDecrypt = await VerifyAsync(sourceStream, content); + if (!canDecrypt) + { + throw Log.ErrorAndCreateException("Cannot decrypt source with provided secret"); + } + + // Then, we must create a password, from which the key will be + // derived. This password will be generated from the specified + // passphrase and salt value. The password will be created using + // the specified hash algorithm. Password creation can be done in + // several iterations. + using (var password = new Rfc2898DeriveBytes(content.PassPhrase, saltValueBytes, content.PasswordIterations ?? PasswordIterations)) + { + // Use the password to generate pseudo-random bytes for the encryption + // key. Specify the size of the key in bytes (instead of bits). + var keyBytes = password.GetBytes(Convert.ToInt32((content.KeySize ?? KeySize) / 8)); + + using (var symmetricKey = CreateAlghorithm()) + { + // Generate decryptor from the existing key bytes and initialization + // vector. Key size will be defined based on the number of the key + // bytes. + using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)) + { + // Define memory stream which will be used to hold encrypted data. + + using (var hmac = new HMACSHA256(GetRawKey(content))) + { + // Skip the checksum bytes + sourceStream.Position = sourceStream.Position + (hmac.HashSize / 8); + } + + using (var cryptoStream = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read, true)) + { + // Decrypt + var position = targetStream.Position; + + await cryptoStream.CopyToAsync(targetStream, 4096); + await targetStream.FlushAsync(); + + targetStream.Position = position; + } + } + } + } + } + catch (Exception ex) + { + Log.Error(ex, "Failed to decrypt"); + throw; + } + } + + public async Task VerifyAsync(Stream sourceStream, EncryptionContext encryptionContext) + { + Argument.IsNotNull(() => encryptionContext); + + var error = false; + + var rawKey = GetRawKey(encryptionContext); + + // First, we must compare the key in the source with a new key created for the data portion of the file. + // Read hash and compute the hash of the remaining contents of the file. + // If the keys are not equals, then parameter key not valid or data has been tampered with. + using (var hmac = new HMACSHA256(rawKey)) + { + var storedHash = new byte[hmac.HashSize / 8]; + + var sourceStreamPosition = sourceStream.Position; + + await sourceStream.ReadAsync(storedHash, 0, storedHash.Length); + + // TODO: Async hashing not supported in NET Core 3.1 + var computedHash = hmac.ComputeHash(sourceStream); + + for (var i = 0; i < storedHash.Length; i++) + { + if (computedHash[i] != storedHash[i]) + { + error = true; + break; + } + } + + sourceStream.Position = sourceStreamPosition; + } + + return !error; + } + + private byte[] GetRawKey(EncryptionContext context) + { + Argument.IsNotNullOrEmpty(() => context.PassPhrase); + var rawKey = Encoding.UTF8.GetBytes(context.PassPhrase); + return rawKey; + } + + protected virtual SymmetricAlgorithm CreateAlghorithm() + { + var aes = Aes.Create(); + + // It is reasonable to set encryption mode to Cipher Block Chaining + // (CBC) and Padding PKCS7. Use default options for other symmetric key parameters. + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.PKCS7; + aes.BlockSize = 128; + + return aes; + } + } +} diff --git a/src/Orc.SupportPackage/Cryptography/Globals.cs b/src/Orc.SupportPackage/Cryptography/Globals.cs new file mode 100644 index 00000000..18a79b24 --- /dev/null +++ b/src/Orc.SupportPackage/Cryptography/Globals.cs @@ -0,0 +1,8 @@ +namespace Orc.SupportPackage +{ + public class Globals + { + public static bool EnableEncryption { get; set; } = false; + public static string EncryptionPassword { get; set; } + } +} diff --git a/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs b/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs new file mode 100644 index 00000000..013a8af9 --- /dev/null +++ b/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs @@ -0,0 +1,12 @@ +namespace Orc.SupportPackage +{ + using System.IO; + using System.Threading.Tasks; + + public interface IEncryptionService + { + Task DecryptAsync(Stream sourceStream, Stream targetStream, EncryptionContext content); + Task EncryptAsync(Stream sourceStream, Stream targetStream, EncryptionContext content); + Task VerifyAsync(Stream sourceStream, EncryptionContext encryptionContext); + } +} \ No newline at end of file diff --git a/src/Orc.SupportPackage/Models/SupportPackageOptions.cs b/src/Orc.SupportPackage/Models/SupportPackageOptions.cs new file mode 100644 index 00000000..db03be4e --- /dev/null +++ b/src/Orc.SupportPackage/Models/SupportPackageOptions.cs @@ -0,0 +1,17 @@ +namespace Orc.SupportPackage +{ + using System.Text; + + /// + /// Contains an additional options on extending support package + /// + public class SupportPackageOptions + { + /// + /// The description metadata builder will be written to SupportPackageOptions.txt + /// + public StringBuilder DescriptionBuilder { get; set; } + + public string[] FileSystemPaths { get; set; } + } +} diff --git a/src/Orc.SupportPackage/ModuleInitializer.cs b/src/Orc.SupportPackage/ModuleInitializer.cs index 851207fc..8a8615ec 100644 --- a/src/Orc.SupportPackage/ModuleInitializer.cs +++ b/src/Orc.SupportPackage/ModuleInitializer.cs @@ -16,8 +16,9 @@ public static void Initialize() serviceLocator.RegisterType(); serviceLocator.RegisterType(); + serviceLocator.RegisterType(); var languageService = serviceLocator.ResolveType(); languageService.RegisterLanguageSource(new LanguageResourceSource("Orc.SupportPackage", "Orc.SupportPackage.Properties", "Resources")); } -} \ No newline at end of file +} diff --git a/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs b/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs index 2124af36..3864067b 100644 --- a/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs +++ b/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) 2008 - 2015 WildGums. All rights reserved. // @@ -8,11 +8,14 @@ namespace Orc.SupportPackage { using System.Threading.Tasks; + using MethodTimer; public interface ISupportPackageService { #region Methods Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns); + + Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns, SupportPackageOptions extendedOptions); #endregion } -} \ No newline at end of file +} diff --git a/src/Orc.SupportPackage/Services/SupportPackageService.cs b/src/Orc.SupportPackage/Services/SupportPackageService.cs index 31544926..4c31febd 100644 --- a/src/Orc.SupportPackage/Services/SupportPackageService.cs +++ b/src/Orc.SupportPackage/Services/SupportPackageService.cs @@ -8,6 +8,7 @@ namespace Orc.SupportPackage { using System; + using System.Collections.Generic; using System.Drawing.Imaging; using System.IO; using System.IO.Compression; @@ -30,33 +31,46 @@ namespace Orc.SupportPackage public class SupportPackageService : ISupportPackageService { + private const int DirectorySizeLimitInBytes = 25 * 1024 * 1024; + private const string CustomDataFolderName = "CustomData"; + private static readonly ILog Log = LogManager.GetCurrentClassLogger(); private readonly IScreenCaptureService _screenCaptureService; private readonly ITypeFactory _typeFactory; private readonly IDirectoryService _directoryService; private readonly IAppDataService _appDataService; + private readonly IEncryptionService _encryptionService; + private readonly IFileService _fileService; private readonly ISystemInfoService _systemInfoService; - public SupportPackageService(ISystemInfoService systemInfoService, - IScreenCaptureService screenCaptureService, ITypeFactory typeFactory, - IDirectoryService directoryService, IAppDataService appDataService) + public SupportPackageService(ISystemInfoService systemInfoService, IScreenCaptureService screenCaptureService, ITypeFactory typeFactory, + IDirectoryService directoryService, IAppDataService appDataService, IEncryptionService encryptionService, IFileService fileService) { Argument.IsNotNull(() => systemInfoService); Argument.IsNotNull(() => screenCaptureService); Argument.IsNotNull(() => typeFactory); Argument.IsNotNull(() => directoryService); Argument.IsNotNull(() => appDataService); + Argument.IsNotNull(() => encryptionService); + Argument.IsNotNull(() => fileService); _systemInfoService = systemInfoService; _screenCaptureService = screenCaptureService; _typeFactory = typeFactory; _directoryService = directoryService; _appDataService = appDataService; + _encryptionService = encryptionService; + _fileService = fileService; } - [Time] public virtual async Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns) + { + return await CreateSupportPackageAsync(zipFileName, directories, excludeFileNamePatterns, null); + } + + [Time] + public virtual async Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns, SupportPackageOptions extendedOptions) { Argument.IsNotNullOrEmpty(() => zipFileName); @@ -68,6 +82,9 @@ public virtual async Task CreateSupportPackageAsync(string zipFileName, st using (var supportPackageContext = new SupportPackageContext()) { + supportPackageContext.AddExcludeFileNamePatterns(excludeFileNamePatterns); + supportPackageContext.AddArtifactDirectories(directories); + // Note: screenshot first, see remarks in screenshot method var screenshotFileName = supportPackageContext.GetFile("screenshot.jpg"); await CaptureWindowAndSaveAsync(screenshotFileName); @@ -95,48 +112,35 @@ public virtual async Task CreateSupportPackageAsync(string zipFileName, st Log.Warning(ex, "Failed to gather support package info from '{0}'. Info will be excluded from the package", supportPackageProviderType.FullName); } } - using (var fileStream = new FileStream(zipFileName, FileMode.OpenOrCreate)) + + if (Globals.EnableEncryption) { - using (var zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Update)) + using (var memoryStream = new MemoryStream()) { - zipArchive.CreateEntryFromDirectory(_appDataService.GetApplicationDataDirectory(Catel.IO.ApplicationDataTarget.UserRoaming), "AppData", CompressionLevel.Optimal); - zipArchive.CreateEntryFromDirectory(supportPackageContext.RootDirectory, string.Empty, CompressionLevel.Optimal); - - if (directories is not null && directories.Length > 0) + using (var fileStream = new FileStream(zipFileName, FileMode.OpenOrCreate)) { - foreach (var directory in directories) + await ZipSupportPackageContentAsync(memoryStream, supportPackageContext); + if (extendedOptions is not null) { - if (!_directoryService.Exists(directory)) - { - Log.Warning($"Directory '{directory}' does not exist, skipping"); - continue; - } - - var directoryPathInArchive = directory.TrimEnd('\\').Split('\\').LastOrDefault(); - if (!string.IsNullOrEmpty(directoryPathInArchive)) - { - zipArchive.CreateEntryFromDirectory(directory, string.Empty, CompressionLevel.Optimal); - } + await ZipCustomAddedContentAsync(memoryStream, extendedOptions.FileSystemPaths.ToList(), extendedOptions.DescriptionBuilder); } + + await _encryptionService.EncryptAsync(memoryStream, fileStream, new EncryptionContext + { + PassPhrase = "wtDtCZd4%pA=F=Dp" + }); } - - if (excludeFileNamePatterns is not null && excludeFileNamePatterns.Length > 0) + } + } + else + { + using (var fileStream = new FileStream(zipFileName, FileMode.OpenOrCreate)) + { + await ZipSupportPackageContentAsync(fileStream, supportPackageContext); + if (extendedOptions is not null) { - Log.Info("Removing excluded files..."); - - var excludeFileNameRegexes = excludeFileNamePatterns.Select(s => new Regex(s.Replace("*", ".*").Replace(".", "\\.") + "$", RegexOptions.IgnoreCase | RegexOptions.Compiled)).ToList(); - - var zipEntries = zipArchive.Entries.ToList(); - foreach (var zipEntry in zipEntries) - { - if (excludeFileNameRegexes.Any(regex => regex.IsMatch(zipEntry.FullName))) - { - zipEntry.Delete(); - } - } + await ZipCustomAddedContentAsync(fileStream, extendedOptions.FileSystemPaths.ToList(), extendedOptions.DescriptionBuilder); } - - await fileStream.FlushAsync(); } } } @@ -152,6 +156,118 @@ public virtual async Task CreateSupportPackageAsync(string zipFileName, st return result; } + private async Task ZipSupportPackageContentAsync(Stream stream, SupportPackageContext supportPackageContext) + { + var directories = supportPackageContext.ArtifactsDirectories; + var excludeFileNamePatterns = supportPackageContext.ExcludeFileNamePatterns; + + using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Update, true)) + { + zipArchive.CreateEntryFromDirectory(_appDataService.GetApplicationDataDirectory(Catel.IO.ApplicationDataTarget.UserRoaming), "AppData", CompressionLevel.Optimal); + zipArchive.CreateEntryFromDirectory(supportPackageContext.RootDirectory, string.Empty, CompressionLevel.Optimal); + + if (directories is not null && directories.Count > 0) + { + foreach (var directory in directories) + { + if (!_directoryService.Exists(directory)) + { + Log.Warning($"Directory '{directory}' does not exist, skipping"); + continue; + } + + var directoryPathInArchive = directory.TrimEnd('\\').Split('\\').LastOrDefault(); + if (!string.IsNullOrEmpty(directoryPathInArchive)) + { + zipArchive.CreateEntryFromDirectory(directory, string.Empty, CompressionLevel.Optimal); + } + } + } + + if (excludeFileNamePatterns is not null && excludeFileNamePatterns.Count > 0) + { + Log.Info("Removing excluded files..."); + + var excludeFileNameRegexes = excludeFileNamePatterns.Select(s => new Regex(s.Replace("*", ".*").Replace(".", "\\.") + "$", RegexOptions.IgnoreCase | RegexOptions.Compiled)).ToList(); + + var zipEntries = zipArchive.Entries.ToList(); + foreach (var zipEntry in zipEntries) + { + if (excludeFileNameRegexes.Any(regex => regex.IsMatch(zipEntry.FullName))) + { + zipEntry.Delete(); + } + } + } + + await stream.FlushAsync(); + } + + } + + private async Task ZipCustomAddedContentAsync(Stream stream, List customArtifactsDataPath, StringBuilder builder) + { + using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Update, true)) + { + foreach (var path in customArtifactsDataPath) + { + try + { + var directoryInfo = new DirectoryInfo(path); + if (directoryInfo.Exists) + { + var directorySize = directoryInfo.GetFiles("*.*", SearchOption.AllDirectories).Sum(info => info.Length); + if (directorySize > DirectorySizeLimitInBytes) + { + Log.Info("Skipped directory '{0}' because its size is greater than '{1}' bytes", path, DirectorySizeLimitInBytes); + + builder.AppendLine("- Directory (skipped): " + path); + } + else + { + zipArchive.CreateEntryFromDirectory(path, Path.Combine(CustomDataFolderName, directoryInfo.Name), CompressionLevel.Optimal); + builder.AppendLine("- Directory: " + path); + } + } + } + catch (Exception ex) + { + Log.Warning(ex); + } + + try + { + if (_fileService.Exists(path)) + { + zipArchive.CreateEntryFromAny(path, CustomDataFolderName, CompressionLevel.Optimal); + builder.AppendLine("- File: " + path); + } + } + catch (Exception ex) + { + Log.Warning(ex); + } + + } + + builder.AppendLine(); + builder.AppendLine("## File system entries"); + builder.AppendLine(); + builder.AppendLine("- Total: " + zipArchive.Entries.Count); + builder.AppendLine("- Files: " + zipArchive.Entries.Count(entry => !entry.Name.EndsWith("/"))); + builder.AppendLine("- Directories: " + zipArchive.Entries.Count(entry => entry.Name.EndsWith("/"))); + + var builderEntry = zipArchive.CreateEntry("SupportPackageOptions.txt"); + + using (var streamWriter = new StreamWriter(builderEntry.Open())) + { + await streamWriter.WriteAsync(builder.ToString()); + } + + await stream.FlushAsync(); + } + } + private async Task CaptureWindowAndSaveAsync(string screenshotFile) { Argument.IsNotNullOrEmpty(() => screenshotFile); From 61d2507d7058b58fbaaedcc680b562b6d0871371 Mon Sep 17 00:00:00 2001 From: Artem Saikin Date: Tue, 17 May 2022 02:52:38 +0300 Subject: [PATCH 2/6] Replace symmetric encryption to asymmetric --- .../Cryptography/EncryptionContext.cs | 16 +- .../Cryptography/EncryptionService.cs | 220 ++++++------------ .../Interfaces/IEncryptionService.cs | 5 +- .../Services/SupportPackageService.cs | 2 +- 4 files changed, 89 insertions(+), 154 deletions(-) diff --git a/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs b/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs index 0671febd..0a083bde 100644 --- a/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs +++ b/src/Orc.SupportPackage/Cryptography/EncryptionContext.cs @@ -14,23 +14,31 @@ public EncryptionContext(EncryptionContext context) { Argument.IsNotNull(() => context); - PassPhrase = context.PassPhrase; + PublicKey = context.PublicKey; SaltValue = context.SaltValue; PasswordIterations = context.PasswordIterations; InitVector = context.InitVector; KeySize = context.KeySize; } - public EncryptionContext(string passPhrase, string saltValue, int? passwordIterations, string initVector, int? keySize) + public EncryptionContext(string publicKey, string saltValue, int? passwordIterations, string initVector, int? keySize) { - PassPhrase = passPhrase; + PublicKey = publicKey; SaltValue = saltValue; PasswordIterations = passwordIterations; InitVector = initVector; KeySize = keySize; } - public string PassPhrase { get; set; } + /// + /// Base64-encrypted xml print of generated public key + /// + public string PublicKey { get; set; } + + /// + /// Path to private key on disk + /// + public string PrivateKeyPath { get; set; } public string SaltValue { get; set; } diff --git a/src/Orc.SupportPackage/Cryptography/EncryptionService.cs b/src/Orc.SupportPackage/Cryptography/EncryptionService.cs index 463c2ab2..8d2b60b2 100644 --- a/src/Orc.SupportPackage/Cryptography/EncryptionService.cs +++ b/src/Orc.SupportPackage/Cryptography/EncryptionService.cs @@ -7,16 +7,27 @@ using System.Threading.Tasks; using Catel; using Catel.Logging; + using Orc.FileSystem; public class EncryptionService : IEncryptionService { private static readonly ILog Log = LogManager.GetCurrentClassLogger(); - private const int PasswordIterations = 2071; - private const int KeySize = 256; + private const int KeySize = 2048; - private const string SaltValue = "5Sr>3rCxUTf@3~Uu"; - private const string InitVector = "fe,r7 fileService); + + _fileService = fileService; + } public async Task EncryptAsync( Stream sourceStream, @@ -25,59 +36,23 @@ public async Task EncryptAsync( { try { - Argument.IsNotNullOrEmpty(() => content.PassPhrase); - - // Convert strings into byte arrays. - // Let us assume that strings only contain ASCII codes. - // If strings include Unicode characters, use Unicode, UTF7, or UTF8 encoding. - var initVectorBytes = Encoding.UTF8.GetBytes(content.InitVector ?? InitVector); - var saltValueBytes = Encoding.UTF8.GetBytes(content.SaltValue ?? SaltValue); - - // First, we must create a password, from which the key will be derived. - // This password will be generated from the specified passphrase and - // salt value. The password will be created using the specified hash - // algorithm. Password creation can be done in several iterations. - using (var password = new Rfc2898DeriveBytes(content.PassPhrase, saltValueBytes, content.PasswordIterations ?? PasswordIterations)) + Argument.IsNotNullOrEmpty(() => content.PublicKey); + + using (var rsa = CreateAlghorithm()) { - using (var hmac = new HMACSHA256(GetRawKey(content))) + var publicRaw = Convert.FromBase64String(content.PublicKey); + + rsa.ImportRSAPublicKey(publicRaw, out _); + + using (var memoryStream = new MemoryStream()) { - // Use the password to generate pseudo-random bytes for the encryption - // key. Specify the size of the key in bytes (instead of bits). - var keyBytes = password.GetBytes(Convert.ToInt32((content.KeySize ?? KeySize) / 8)); - var hashBytesSize = hmac.HashSize / 8; - - using (var symmetricKey = CreateAlghorithm()) - { - // Generate encryptor from the existing key bytes and initialization - // vector. Key size will be defined based on the number of the key - // bytes. - using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)) - { - // Define cryptographic stream (always use Write mode for encryption) - using (var cryptoStream = new CryptoStream(targetStream, encryptor, CryptoStreamMode.Write, true)) - { - targetStream.Seek(hashBytesSize, SeekOrigin.Begin); - - await sourceStream.CopyToAsync(cryptoStream, 4096); - await cryptoStream.FlushAsync(); - - // Finish encrypting -#pragma warning disable CL0001 // Use async overload inside this async method - cryptoStream.FlushFinalBlock(); -#pragma warning restore CL0001 // Use async overload inside this async method - - await targetStream.FlushAsync(); - } - } - - targetStream.Seek(hashBytesSize, SeekOrigin.Begin); - var hmacbytes = hmac.ComputeHash(targetStream); - - targetStream.Seek(0, SeekOrigin.Begin); - targetStream.Write(hmacbytes, 0, hmacbytes.Length); // Always 32 bytes - - await targetStream.FlushAsync(); - } + sourceStream.CopyTo(memoryStream); + + var buffer = memoryStream.ToArray(); + var encryptedByres = rsa.Encrypt(buffer, RSAEncryptionPadding.OaepSHA256); + + targetStream.Write(encryptedByres, 0, encryptedByres.Length); + await targetStream.FlushAsync(); } } } @@ -92,64 +67,35 @@ public async Task EncryptAsync( public async Task DecryptAsync( Stream sourceStream, Stream targetStream, - EncryptionContext content) + EncryptionContext context) { try { - Argument.IsNotNullOrEmpty(() => content.PassPhrase); + Argument.IsNotNullOrEmpty(() => context.PrivateKeyPath); - // Convert strings defining encryption key characteristics into byte - // arrays. Let us assume that strings only contain ASCII codes. - // If strings include Unicode characters, use Unicode, UTF7, or UTF8 - // encoding. - var initVectorBytes = Encoding.UTF8.GetBytes(content.InitVector ?? InitVector); - var saltValueBytes = Encoding.UTF8.GetBytes(content.SaltValue ?? SaltValue); - - var canDecrypt = await VerifyAsync(sourceStream, content); - if (!canDecrypt) + using (var rsa = CreateAlghorithm()) { - throw Log.ErrorAndCreateException("Cannot decrypt source with provided secret"); - } + // Read secret keeping only the payload of the key + var pemText = await _fileService.ReadAllTextAsync(context.PrivateKeyPath); + pemText = pemText.Replace(PemTrailingPrivate, ""); + pemText = pemText.Replace(PemLeadingPublic, ""); - // Then, we must create a password, from which the key will be - // derived. This password will be generated from the specified - // passphrase and salt value. The password will be created using - // the specified hash algorithm. Password creation can be done in - // several iterations. - using (var password = new Rfc2898DeriveBytes(content.PassPhrase, saltValueBytes, content.PasswordIterations ?? PasswordIterations)) - { - // Use the password to generate pseudo-random bytes for the encryption - // key. Specify the size of the key in bytes (instead of bits). - var keyBytes = password.GetBytes(Convert.ToInt32((content.KeySize ?? KeySize) / 8)); + var secretRaw = Convert.FromBase64String(pemText); + + rsa.ImportRSAPrivateKey(secretRaw, out _); - using (var symmetricKey = CreateAlghorithm()) + using (var memoryStream = new MemoryStream()) { - // Generate decryptor from the existing key bytes and initialization - // vector. Key size will be defined based on the number of the key - // bytes. - using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)) - { - // Define memory stream which will be used to hold encrypted data. - - using (var hmac = new HMACSHA256(GetRawKey(content))) - { - // Skip the checksum bytes - sourceStream.Position = sourceStream.Position + (hmac.HashSize / 8); - } - - using (var cryptoStream = new CryptoStream(sourceStream, decryptor, CryptoStreamMode.Read, true)) - { - // Decrypt - var position = targetStream.Position; - - await cryptoStream.CopyToAsync(targetStream, 4096); - await targetStream.FlushAsync(); - - targetStream.Position = position; - } - } + sourceStream.CopyTo(memoryStream); + + var buffer = memoryStream.ToArray(); + var decryptedBytes = rsa.Decrypt(buffer, RSAEncryptionPadding.OaepSHA256); + + targetStream.Write(decryptedBytes, 0, decryptedBytes.Length); + await targetStream.FlushAsync(); } } + } catch (Exception ex) { @@ -158,61 +104,41 @@ public async Task DecryptAsync( } } - public async Task VerifyAsync(Stream sourceStream, EncryptionContext encryptionContext) + protected virtual RSA CreateAlghorithm() { - Argument.IsNotNull(() => encryptionContext); - - var error = false; - - var rawKey = GetRawKey(encryptionContext); + var rsa = new RSACryptoServiceProvider(KeySize); + return rsa; + } - // First, we must compare the key in the source with a new key created for the data portion of the file. - // Read hash and compute the hash of the remaining contents of the file. - // If the keys are not equals, then parameter key not valid or data has been tampered with. - using (var hmac = new HMACSHA256(rawKey)) + public void Generate(string secretPath, string publicKeyPath) + { + using (var rsa = CreateAlghorithm()) { - var storedHash = new byte[hmac.HashSize / 8]; + var privateKey = rsa.ExportRSAPrivateKey(); + var publicKey = rsa.ExportRSAPublicKey(); - var sourceStreamPosition = sourceStream.Position; + var privateKeyPem = Convert.ToBase64String(privateKey); + var publicKeyPem = Convert.ToBase64String(publicKey); - await sourceStream.ReadAsync(storedHash, 0, storedHash.Length); + var stringBuilder = new StringBuilder(); - // TODO: Async hashing not supported in NET Core 3.1 - var computedHash = hmac.ComputeHash(sourceStream); + stringBuilder.AppendLine(PemLeadingPrivate); + stringBuilder.AppendLine(privateKeyPem); + stringBuilder.AppendLine(PemTrailingPrivate); - for (var i = 0; i < storedHash.Length; i++) - { - if (computedHash[i] != storedHash[i]) - { - error = true; - break; - } - } + var privateTextPem = stringBuilder.ToString(); - sourceStream.Position = sourceStreamPosition; - } - - return !error; - } - - private byte[] GetRawKey(EncryptionContext context) - { - Argument.IsNotNullOrEmpty(() => context.PassPhrase); - var rawKey = Encoding.UTF8.GetBytes(context.PassPhrase); - return rawKey; - } + stringBuilder.Clear(); - protected virtual SymmetricAlgorithm CreateAlghorithm() - { - var aes = Aes.Create(); + stringBuilder.AppendLine(PemLeadingPublic); + stringBuilder.AppendLine(publicKeyPem); + stringBuilder.AppendLine(PemTrailingPublic); - // It is reasonable to set encryption mode to Cipher Block Chaining - // (CBC) and Padding PKCS7. Use default options for other symmetric key parameters. - aes.Mode = CipherMode.CBC; - aes.Padding = PaddingMode.PKCS7; - aes.BlockSize = 128; + var publicTextPem = stringBuilder.ToString(); - return aes; + _fileService.WriteAllText(secretPath, privateTextPem); + _fileService.WriteAllText(publicKeyPath, publicKeyPem); + } } } } diff --git a/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs b/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs index 013a8af9..d51aeb27 100644 --- a/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs +++ b/src/Orc.SupportPackage/Cryptography/Interfaces/IEncryptionService.cs @@ -7,6 +7,7 @@ public interface IEncryptionService { Task DecryptAsync(Stream sourceStream, Stream targetStream, EncryptionContext content); Task EncryptAsync(Stream sourceStream, Stream targetStream, EncryptionContext content); - Task VerifyAsync(Stream sourceStream, EncryptionContext encryptionContext); + + public void Generate(string secretLocation, string publicKeyLocation); } -} \ No newline at end of file +} diff --git a/src/Orc.SupportPackage/Services/SupportPackageService.cs b/src/Orc.SupportPackage/Services/SupportPackageService.cs index 4c31febd..73d1afd4 100644 --- a/src/Orc.SupportPackage/Services/SupportPackageService.cs +++ b/src/Orc.SupportPackage/Services/SupportPackageService.cs @@ -127,7 +127,7 @@ public virtual async Task CreateSupportPackageAsync(string zipFileName, st await _encryptionService.EncryptAsync(memoryStream, fileStream, new EncryptionContext { - PassPhrase = "wtDtCZd4%pA=F=Dp" + PublicKey = "wtDtCZd4%pA=F=Dp" }); } } From 486828f2a9a9570dd6788dbd76d83d35af981ea1 Mon Sep 17 00:00:00 2001 From: Artem Saikin Date: Tue, 17 May 2022 11:36:29 +0300 Subject: [PATCH 3/6] Refactor code to avoid multiple parameters to services --- src/Orc.SupportPackage.Example/App.xaml.cs | 2 - .../Context/SupportPackageBuilderContext.cs | 16 +++ .../ISupportPackageBuilderService.cs | 4 +- .../Services/SupportPackageBuilderService.cs | 33 ++++-- .../Context/SupportPackageContext.cs | 35 ++++++ .../Cryptography/Globals.cs | 8 -- .../Models/SupportPackageOptions.cs | 17 --- .../Interfaces/ISupportPackageService.cs | 2 +- .../Services/SupportPackageService.cs | 103 +++++++++--------- 9 files changed, 131 insertions(+), 89 deletions(-) create mode 100644 src/Orc.SupportPackage.Xaml/Context/SupportPackageBuilderContext.cs delete mode 100644 src/Orc.SupportPackage/Cryptography/Globals.cs delete mode 100644 src/Orc.SupportPackage/Models/SupportPackageOptions.cs diff --git a/src/Orc.SupportPackage.Example/App.xaml.cs b/src/Orc.SupportPackage.Example/App.xaml.cs index f7e1474b..ba3ae47c 100644 --- a/src/Orc.SupportPackage.Example/App.xaml.cs +++ b/src/Orc.SupportPackage.Example/App.xaml.cs @@ -35,8 +35,6 @@ protected override void OnStartup(StartupEventArgs e) this.ApplyTheme(); - Globals.EnableEncryption = true; - base.OnStartup(e); } } diff --git a/src/Orc.SupportPackage.Xaml/Context/SupportPackageBuilderContext.cs b/src/Orc.SupportPackage.Xaml/Context/SupportPackageBuilderContext.cs new file mode 100644 index 00000000..407508b0 --- /dev/null +++ b/src/Orc.SupportPackage.Xaml/Context/SupportPackageBuilderContext.cs @@ -0,0 +1,16 @@ +namespace Orc.SupportPackage +{ + using System.Collections.Generic; + + public class SupportPackageBuilderContext + { + /// + /// Name of support package file + /// + public string FileName { get; set; } + + public List Artifacts { get; set; } + + public bool EnableEncryption { get; set; } + } +} diff --git a/src/Orc.SupportPackage.Xaml/Services/Interfaces/ISupportPackageBuilderService.cs b/src/Orc.SupportPackage.Xaml/Services/Interfaces/ISupportPackageBuilderService.cs index b32b0f39..18c61fc6 100644 --- a/src/Orc.SupportPackage.Xaml/Services/Interfaces/ISupportPackageBuilderService.cs +++ b/src/Orc.SupportPackage.Xaml/Services/Interfaces/ISupportPackageBuilderService.cs @@ -13,6 +13,8 @@ public interface ISupportPackageBuilderService { #region Methods Task CreateSupportPackageAsync(string fileName, List artifacts); + Task CreateSupportPackageAsync(SupportPackageBuilderContext context); + #endregion } -} \ No newline at end of file +} diff --git a/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs b/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs index 8cefe176..8470e407 100644 --- a/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs +++ b/src/Orc.SupportPackage.Xaml/Services/SupportPackageBuilderService.cs @@ -7,10 +7,7 @@ namespace Orc.SupportPackage { - using System; using System.Collections.Generic; - using System.IO; - using System.IO.Compression; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -43,6 +40,18 @@ public SupportPackageBuilderService(ISupportPackageService supportPackageService #region Methods public virtual async Task CreateSupportPackageAsync(string fileName, List artifacts) { + return await CreateSupportPackageAsync(new SupportPackageBuilderContext + { + FileName = fileName, + Artifacts = artifacts, + }); + } + + public async Task CreateSupportPackageAsync(SupportPackageBuilderContext context) + { + var fileName = context.FileName; + var artifacts = context.Artifacts; + Argument.IsNotNullOrWhitespace(() => fileName); Argument.IsNotNull(() => artifacts); @@ -89,16 +98,20 @@ public virtual async Task CreateSupportPackageAsync(string fileName, List< builder.AppendLine(); } - var result = await _supportPackageService.CreateSupportPackageAsync(fileName, directories, excludeFileNamePatterns, new SupportPackageOptions + using (var supportPackageContext = new SupportPackageContext()) { - FileSystemPaths = customData.ToArray(), - DescriptionBuilder = builder - }); - - return result; - } + supportPackageContext.ZipFileName = fileName; + supportPackageContext.AddArtifactDirectories(directories); + supportPackageContext.AddExcludeFileNamePatterns(excludeFileNamePatterns); + supportPackageContext.AddCustomFileSystemPaths(customData.ToArray()); + supportPackageContext.DescriptionBuilder = builder; + supportPackageContext.EnableEncryption = context.EnableEncryption; + var result = await _supportPackageService.CreateSupportPackageAsync(supportPackageContext); + return result; + } + } #endregion } diff --git a/src/Orc.SupportPackage/Context/SupportPackageContext.cs b/src/Orc.SupportPackage/Context/SupportPackageContext.cs index 14f2b37a..97590279 100644 --- a/src/Orc.SupportPackage/Context/SupportPackageContext.cs +++ b/src/Orc.SupportPackage/Context/SupportPackageContext.cs @@ -10,6 +10,7 @@ namespace Orc.SupportPackage using System; using System.Collections.Generic; using System.IO; + using System.Text; using Catel; using Catel.Logging; using Catel.Reflection; @@ -22,6 +23,7 @@ public class SupportPackageContext : Disposable, ISupportPackageContext private readonly List _artifactsDirectories = new(); private readonly List _excludefileNamePatterns = new(); + private readonly List _customFileSystemPaths = new(); public SupportPackageContext() { @@ -35,10 +37,18 @@ public SupportPackageContext() public string RootDirectory => _rootDirectory; + public string ZipFileName { get; set; } + + public bool EnableEncryption { get; set; } + + public StringBuilder DescriptionBuilder { get; set; } + public IReadOnlyCollection ExcludeFileNamePatterns => _excludefileNamePatterns; public IReadOnlyCollection ArtifactsDirectories => _artifactsDirectories; + public IReadOnlyCollection CustomFileSystemPaths => _customFileSystemPaths; + public string GetDirectory(string relativeDirectoryName) { var fullPath = Path.Combine(_rootDirectory, relativeDirectoryName); @@ -66,14 +76,34 @@ public string GetFile(string relativeFilePath) public void AddArtifactDirectories(string[] directories) { + if (directories is null) + { + return; + } + _artifactsDirectories.AddRange(directories); } public void AddExcludeFileNamePatterns(string[] fileNamePatterns) { + if (fileNamePatterns is null) + { + return; + } + _excludefileNamePatterns.AddRange(fileNamePatterns); } + public void AddCustomFileSystemPaths(string[] fileSystemPaths) + { + if (fileSystemPaths is null) + { + return; + } + + _customFileSystemPaths.AddRange(fileSystemPaths); + } + protected override void DisposeManaged() { Log.Info("Deleting temporary files from '{0}'", _rootDirectory); @@ -84,6 +114,11 @@ protected override void DisposeManaged() { Directory.Delete(_rootDirectory, true); } + + if (DescriptionBuilder is not null) + { + DescriptionBuilder.Clear(); + } } catch (Exception ex) { diff --git a/src/Orc.SupportPackage/Cryptography/Globals.cs b/src/Orc.SupportPackage/Cryptography/Globals.cs deleted file mode 100644 index 18a79b24..00000000 --- a/src/Orc.SupportPackage/Cryptography/Globals.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Orc.SupportPackage -{ - public class Globals - { - public static bool EnableEncryption { get; set; } = false; - public static string EncryptionPassword { get; set; } - } -} diff --git a/src/Orc.SupportPackage/Models/SupportPackageOptions.cs b/src/Orc.SupportPackage/Models/SupportPackageOptions.cs deleted file mode 100644 index db03be4e..00000000 --- a/src/Orc.SupportPackage/Models/SupportPackageOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Orc.SupportPackage -{ - using System.Text; - - /// - /// Contains an additional options on extending support package - /// - public class SupportPackageOptions - { - /// - /// The description metadata builder will be written to SupportPackageOptions.txt - /// - public StringBuilder DescriptionBuilder { get; set; } - - public string[] FileSystemPaths { get; set; } - } -} diff --git a/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs b/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs index 3864067b..023eab4c 100644 --- a/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs +++ b/src/Orc.SupportPackage/Services/Interfaces/ISupportPackageService.cs @@ -15,7 +15,7 @@ public interface ISupportPackageService #region Methods Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns); - Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns, SupportPackageOptions extendedOptions); + Task CreateSupportPackageAsync(SupportPackageContext supportPackageContext); #endregion } } diff --git a/src/Orc.SupportPackage/Services/SupportPackageService.cs b/src/Orc.SupportPackage/Services/SupportPackageService.cs index 73d1afd4..814b3f71 100644 --- a/src/Orc.SupportPackage/Services/SupportPackageService.cs +++ b/src/Orc.SupportPackage/Services/SupportPackageService.cs @@ -66,13 +66,21 @@ public SupportPackageService(ISystemInfoService systemInfoService, IScreenCaptur public virtual async Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns) { - return await CreateSupportPackageAsync(zipFileName, directories, excludeFileNamePatterns, null); + using (var packageContext = new SupportPackageContext + { + ZipFileName = zipFileName, + }) + { + packageContext.AddExcludeFileNamePatterns(excludeFileNamePatterns); + + return await CreateSupportPackageAsync(packageContext); + } } [Time] - public virtual async Task CreateSupportPackageAsync(string zipFileName, string[] directories, string[] excludeFileNamePatterns, SupportPackageOptions extendedOptions) + public virtual async Task CreateSupportPackageAsync(SupportPackageContext supportPackageContext) { - Argument.IsNotNullOrEmpty(() => zipFileName); + Argument.IsNotNullOrEmpty(() => supportPackageContext.ZipFileName); var result = true; @@ -80,67 +88,62 @@ public virtual async Task CreateSupportPackageAsync(string zipFileName, st { Log.Info("Creating support package"); - using (var supportPackageContext = new SupportPackageContext()) - { - supportPackageContext.AddExcludeFileNamePatterns(excludeFileNamePatterns); - supportPackageContext.AddArtifactDirectories(directories); - - // Note: screenshot first, see remarks in screenshot method - var screenshotFileName = supportPackageContext.GetFile("screenshot.jpg"); - await CaptureWindowAndSaveAsync(screenshotFileName); + // Note: screenshot first, see remarks in screenshot method + var screenshotFileName = supportPackageContext.GetFile("screenshot.jpg"); + await CaptureWindowAndSaveAsync(screenshotFileName); - var systemInfoXmlFileName = supportPackageContext.GetFile("systeminfo.xml"); - var systemInfoTxtFileName = supportPackageContext.GetFile("systeminfo.txt"); - await GetAndSaveSystemInformationAsync(systemInfoXmlFileName, systemInfoTxtFileName); + var systemInfoXmlFileName = supportPackageContext.GetFile("systeminfo.xml"); + var systemInfoTxtFileName = supportPackageContext.GetFile("systeminfo.txt"); + await GetAndSaveSystemInformationAsync(systemInfoXmlFileName, systemInfoTxtFileName); - var supportPackageProviderTypes = (from type in TypeCache.GetTypes() - where !type.IsAbstractEx() && type.IsClassEx() && - type.ImplementsInterfaceEx() - select type).ToList(); + var supportPackageProviderTypes = (from type in TypeCache.GetTypes() + where !type.IsAbstractEx() && type.IsClassEx() && + type.ImplementsInterfaceEx() + select type).ToList(); - foreach (var supportPackageProviderType in supportPackageProviderTypes) + foreach (var supportPackageProviderType in supportPackageProviderTypes) + { + try { - try - { - Log.Debug("Gathering support package info from '{0}'", supportPackageProviderType.FullName); + Log.Debug("Gathering support package info from '{0}'", supportPackageProviderType.FullName); - var provider = (ISupportPackageProvider)_typeFactory.CreateInstance(supportPackageProviderType); - await provider.ProvideAsync(supportPackageContext); - } - catch (Exception ex) - { - Log.Warning(ex, "Failed to gather support package info from '{0}'. Info will be excluded from the package", supportPackageProviderType.FullName); - } + var provider = (ISupportPackageProvider)_typeFactory.CreateInstance(supportPackageProviderType); + await provider.ProvideAsync(supportPackageContext); } + catch (Exception ex) + { + Log.Warning(ex, "Failed to gather support package info from '{0}'. Info will be excluded from the package", supportPackageProviderType.FullName); + } + } - if (Globals.EnableEncryption) + if (supportPackageContext.EnableEncryption) + { + using (var memoryStream = new MemoryStream()) { - using (var memoryStream = new MemoryStream()) + using (var fileStream = new FileStream(supportPackageContext.ZipFileName, FileMode.OpenOrCreate)) { - using (var fileStream = new FileStream(zipFileName, FileMode.OpenOrCreate)) + await ZipSupportPackageContentAsync(memoryStream, supportPackageContext); + + if (supportPackageContext.CustomFileSystemPaths.Any()) { - await ZipSupportPackageContentAsync(memoryStream, supportPackageContext); - if (extendedOptions is not null) - { - await ZipCustomAddedContentAsync(memoryStream, extendedOptions.FileSystemPaths.ToList(), extendedOptions.DescriptionBuilder); - } - - await _encryptionService.EncryptAsync(memoryStream, fileStream, new EncryptionContext - { - PublicKey = "wtDtCZd4%pA=F=Dp" - }); + await ZipCustomAddedContentAsync(memoryStream, supportPackageContext.CustomFileSystemPaths, supportPackageContext.DescriptionBuilder ?? new StringBuilder()); } + + await _encryptionService.EncryptAsync(memoryStream, fileStream, new EncryptionContext + { + PublicKey = "wtDtCZd4%pA=F=Dp" + }); } } - else + } + else + { + using (var fileStream = new FileStream(supportPackageContext.ZipFileName, FileMode.OpenOrCreate)) { - using (var fileStream = new FileStream(zipFileName, FileMode.OpenOrCreate)) + await ZipSupportPackageContentAsync(fileStream, supportPackageContext); + if (supportPackageContext.CustomFileSystemPaths.Any()) { - await ZipSupportPackageContentAsync(fileStream, supportPackageContext); - if (extendedOptions is not null) - { - await ZipCustomAddedContentAsync(fileStream, extendedOptions.FileSystemPaths.ToList(), extendedOptions.DescriptionBuilder); - } + await ZipCustomAddedContentAsync(fileStream, supportPackageContext.CustomFileSystemPaths, supportPackageContext.DescriptionBuilder ?? new StringBuilder()); } } } @@ -205,7 +208,7 @@ private async Task ZipSupportPackageContentAsync(Stream stream, SupportPackageCo } - private async Task ZipCustomAddedContentAsync(Stream stream, List customArtifactsDataPath, StringBuilder builder) + private async Task ZipCustomAddedContentAsync(Stream stream, IEnumerable customArtifactsDataPath, StringBuilder builder) { using (var zipArchive = new ZipArchive(stream, ZipArchiveMode.Update, true)) { From b7ea9fbb0dfbcf3afff9206d3c974cd3b4e5726d Mon Sep 17 00:00:00 2001 From: Artem Saikin Date: Tue, 17 May 2022 15:14:31 +0300 Subject: [PATCH 4/6] Fix and prepare example for encryption --- .../ViewModels/MainViewModel.cs | 44 +++++++++++++++++-- .../Views/MainView.xaml | 43 +++++++++++++++++- .../Cryptography/EncryptionService.cs | 2 +- 3 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Orc.SupportPackage.Example/ViewModels/MainViewModel.cs b/src/Orc.SupportPackage.Example/ViewModels/MainViewModel.cs index 8b637b71..707eb225 100644 --- a/src/Orc.SupportPackage.Example/ViewModels/MainViewModel.cs +++ b/src/Orc.SupportPackage.Example/ViewModels/MainViewModel.cs @@ -4,15 +4,17 @@ using System.Drawing.Imaging; using System.IO; using System.Linq; + using System.Reflection; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; - using SystemInfo; using Catel; + using Catel.IoC; using Catel.MVVM; using Catel.Services; using Catel.Threading; using Orc.SupportPackage.ViewModels; + using SystemInfo; public class MainViewModel : ViewModelBase { @@ -21,27 +23,42 @@ public class MainViewModel : ViewModelBase private readonly IScreenCaptureService _screenCaptureService; private readonly ISystemInfoService _systemInfoService; + private readonly IMessageService _messageService; private readonly IUIVisualizerService _uiVisualizerService; private readonly IAppDataService _appDataService; + private readonly ITypeFactory _typeFactory; + private readonly IEncryptionService _encryptionService; - public MainViewModel(IScreenCaptureService screenCaptureService, ISystemInfoService systemInfoService, - IUIVisualizerService uiVisualizerService, IAppDataService appDataService) + public MainViewModel(IScreenCaptureService screenCaptureService, ISystemInfoService systemInfoService, IMessageService messageService, + IUIVisualizerService uiVisualizerService, IAppDataService appDataService, ITypeFactory typeFactory, IEncryptionService encryptionService) { Argument.IsNotNull(() => screenCaptureService); Argument.IsNotNull(() => systemInfoService); + Argument.IsNotNull(() => messageService); Argument.IsNotNull(() => uiVisualizerService); Argument.IsNotNull(() => appDataService); + Argument.IsNotNull(() => typeFactory); + Argument.IsNotNull(() => encryptionService); _screenCaptureService = screenCaptureService; _systemInfoService = systemInfoService; + _messageService = messageService; _uiVisualizerService = uiVisualizerService; _appDataService = appDataService; + _typeFactory = typeFactory; + _encryptionService = encryptionService; Screenshot = new TaskCommand(OnScreenshotExecuteAsync); ShowSystemInfo = new TaskCommand(OnShowSystemInfoExecuteAsync); SavePackage = new TaskCommand(OnSavePackageExecuteAsync); + EncryptAndSavePackage = new TaskCommand(OnEncryptAndSavePackageExecuteAsync); + GenerateKeys = new TaskCommand(OnGenerateKeysExecuteAsync); Title = "Orc.SupportPackage example"; + + var currentDirectory = Environment.CurrentDirectory; + PublicKeyPath = Path.Combine(currentDirectory, "public.pem"); + PrivateKeyPath = Path.Combine(currentDirectory, "private.pem"); } #region Commands @@ -52,6 +69,22 @@ private async Task OnSavePackageExecuteAsync() await _uiVisualizerService.ShowDialogAsync(); } + public TaskCommand EncryptAndSavePackage { get; private set; } + + private async Task OnEncryptAndSavePackageExecuteAsync() + { + var supportPackageViewModel = _typeFactory.CreateInstance(); + await _uiVisualizerService.ShowDialogAsync(supportPackageViewModel); + } + + public TaskCommand GenerateKeys { get; private set; } + + private async Task OnGenerateKeysExecuteAsync() + { + _encryptionService.Generate(PrivateKeyPath, PublicKeyPath); + await _messageService.ShowInformationAsync("Encryption keys generated"); + } + public TaskCommand Screenshot { get; private set; } private async Task OnScreenshotExecuteAsync() @@ -85,12 +118,17 @@ private async Task OnShowSystemInfoExecuteAsync() var sysInfoLines = sysInfoElements.Select(x => x.ToString()); SystemInfo = string.Join("\n", sysInfoLines); } + #endregion #region Properties public BitmapImage ScreenPic { get; private set; } public string SystemInfo { get; set; } + + public string PrivateKeyPath { get; set; } + + public string PublicKeyPath { get; set; } #endregion } } diff --git a/src/Orc.SupportPackage.Example/Views/MainView.xaml b/src/Orc.SupportPackage.Example/Views/MainView.xaml index 50488903..44f2b70f 100644 --- a/src/Orc.SupportPackage.Example/Views/MainView.xaml +++ b/src/Orc.SupportPackage.Example/Views/MainView.xaml @@ -41,7 +41,48 @@ - + +