1
0
mirror of https://git.teknik.io/Teknikode/Teknik.git synced 2023-08-02 14:16:22 +02:00

Implement separate Storage Service to decouple file storage for uploads and pastes

This commit is contained in:
Uncled1023 2021-08-06 22:26:09 -07:00
parent 3ffac5959f
commit 2331d1dd9f
18 changed files with 369 additions and 191 deletions

View File

@ -8,14 +8,12 @@ namespace Teknik.Configuration
public int UrlLength { get; set; } public int UrlLength { get; set; }
public int DeleteKeyLength { get; set; } public int DeleteKeyLength { get; set; }
public string SyntaxVisualStyle { get; set; } public string SyntaxVisualStyle { get; set; }
// Location of the upload directory
public string PasteDirectory { get; set; }
// File Extension for saved files
public string FileExtension { get; set; }
public int KeySize { get; set; } public int KeySize { get; set; }
public int BlockSize { get; set; } public int BlockSize { get; set; }
// The size of the chunk that the file will be encrypted/decrypted in (bytes) // The size of the chunk that the file will be encrypted/decrypted in (bytes)
public int ChunkSize { get; set; } public int ChunkSize { get; set; }
// Storage settings
public StorageConfig StorageConfig { get; set; }
public PasteConfig() public PasteConfig()
{ {
@ -25,9 +23,8 @@ namespace Teknik.Configuration
KeySize = 256; KeySize = 256;
BlockSize = 128; BlockSize = 128;
ChunkSize = 1040; ChunkSize = 1040;
PasteDirectory = Directory.GetCurrentDirectory();
FileExtension = "enc";
SyntaxVisualStyle = "vs"; SyntaxVisualStyle = "vs";
StorageConfig = new StorageConfig("pastes");
} }
} }
} }

View File

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.Configuration
{
public class StorageConfig
{
public StorageType Type { get; set; }
// File Extension for saved files
public string FileExtension { get; set; }
// Length of filename
public int FileNameLength { get; set; }
// Local Storage Options
public string LocalDirectory { get; set; }
// S3 Options
public string Container { get; set; }
public StorageConfig(string container)
{
Type = StorageType.Local;
Container = container;
LocalDirectory = Directory.GetCurrentDirectory();
FileExtension = "enc";
FileNameLength = 10;
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.Configuration
{
public enum StorageType
{
Local,
S3
}
}

View File

@ -19,10 +19,6 @@ namespace Teknik.Configuration
public long MaxTotalSizeBasic { get; set; } public long MaxTotalSizeBasic { get; set; }
// Maximum total size for basic users // Maximum total size for basic users
public long MaxTotalSizePremium { get; set; } public long MaxTotalSizePremium { get; set; }
// Location of the upload directory
public string UploadDirectory { get; set; }
// File Extension for saved files
public string FileExtension { get; set; }
public int UrlLength { get; set; } public int UrlLength { get; set; }
public int DeleteKeyLength { get; set; } public int DeleteKeyLength { get; set; }
public int KeySize { get; set; } public int KeySize { get; set; }
@ -34,6 +30,8 @@ namespace Teknik.Configuration
public ClamConfig ClamConfig { get; set; } public ClamConfig ClamConfig { get; set; }
// Hash Scanning Settings // Hash Scanning Settings
public HashScanConfig HashScanConfig { get; set; } public HashScanConfig HashScanConfig { get; set; }
// Storage settings
public StorageConfig StorageConfig { get; set; }
// Content Type Restrictions // Content Type Restrictions
public List<string> RestrictedContentTypes { get; set; } public List<string> RestrictedContentTypes { get; set; }
public List<string> RestrictedExtensions { get; set; } public List<string> RestrictedExtensions { get; set; }
@ -53,8 +51,6 @@ namespace Teknik.Configuration
MaxDownloadSize = 100000000; MaxDownloadSize = 100000000;
MaxTotalSizeBasic = 1000000000; MaxTotalSizeBasic = 1000000000;
MaxTotalSizePremium = 5000000000; MaxTotalSizePremium = 5000000000;
UploadDirectory = Directory.GetCurrentDirectory();
FileExtension = "enc";
UrlLength = 5; UrlLength = 5;
DeleteKeyLength = 24; DeleteKeyLength = 24;
KeySize = 256; KeySize = 256;
@ -63,6 +59,7 @@ namespace Teknik.Configuration
ChunkSize = 1024; ChunkSize = 1024;
ClamConfig = new ClamConfig(); ClamConfig = new ClamConfig();
HashScanConfig = new HashScanConfig(); HashScanConfig = new HashScanConfig();
StorageConfig = new StorageConfig("uploads");
RestrictedContentTypes = new List<string>(); RestrictedContentTypes = new List<string>();
RestrictedExtensions = new List<string>(); RestrictedExtensions = new List<string>();
} }

View File

@ -1,6 +1,7 @@
using CommandLine; using CommandLine;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using nClam; using nClam;
using StorageService;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -151,9 +152,9 @@ namespace Teknik.ServiceWorker
private static async Task<bool> ScanUpload(Config config, TeknikEntities db, Upload upload, int totalCount, int currentCount) private static async Task<bool> ScanUpload(Config config, TeknikEntities db, Upload upload, int totalCount, int currentCount)
{ {
bool virusDetected = false; bool virusDetected = false;
string subDir = upload.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
string filePath = Path.Combine(config.UploadConfig.UploadDirectory, subDir, upload.FileName); var fileStream = storageService.GetFile(upload.FileName);
if (File.Exists(filePath)) if (fileStream != null)
{ {
// If the IV is set, and Key is set, then scan it // If the IV is set, and Key is set, then scan it
if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV)) if (!string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV))
@ -173,12 +174,11 @@ namespace Teknik.ServiceWorker
} }
} }
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read)) using (AesCounterStream aesStream = new AesCounterStream(fileStream, false, keyBytes, ivBytes))
using (AesCounterStream aesStream = new AesCounterStream(fs, false, keyBytes, ivBytes))
{ {
ClamClient clam = new ClamClient(config.UploadConfig.ClamConfig.Server, config.UploadConfig.ClamConfig.Port); ClamClient clam = new ClamClient(config.UploadConfig.ClamConfig.Server, config.UploadConfig.ClamConfig.Port);
clam.MaxStreamSize = maxUploadSize; clam.MaxStreamSize = maxUploadSize;
ClamScanResult scanResult = await clam.SendAndScanFileAsync(fs); ClamScanResult scanResult = await clam.SendAndScanFileAsync(fileStream);
switch (scanResult.Result) switch (scanResult.Result)
{ {
@ -198,11 +198,12 @@ namespace Teknik.ServiceWorker
lock (dbLock) lock (dbLock)
{ {
string urlName = upload.Url; string urlName = upload.Url;
// Delete from the DB
db.Uploads.Remove(upload);
// Delete the File // Delete the File
DeleteFile(filePath); storageService.DeleteFile(upload.FileName);
// Delete from the DB
db.Uploads.Remove(upload);
// Add to transparency report if any were found // Add to transparency report if any were found
Takedown report = new Takedown(); Takedown report = new Takedown();
@ -244,13 +245,10 @@ namespace Teknik.ServiceWorker
// Process uploads // Process uploads
List<Upload> uploads = db.Uploads.Where(u => u.ExpireDate != null && u.ExpireDate < curDate).ToList(); List<Upload> uploads = db.Uploads.Where(u => u.ExpireDate != null && u.ExpireDate < curDate).ToList();
var uploadStorageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
foreach (Upload upload in uploads) foreach (Upload upload in uploads)
{ {
string subDir = upload.FileName[0].ToString(); DeleteFile(uploadStorageService, upload.FileName);
string filePath = Path.Combine(config.UploadConfig.UploadDirectory, subDir, upload.FileName);
// Delete the File
DeleteFile(filePath);
} }
db.RemoveRange(uploads); db.RemoveRange(uploads);
db.SaveChanges(); db.SaveChanges();
@ -258,13 +256,11 @@ namespace Teknik.ServiceWorker
// Process Pastes // Process Pastes
List<Paste> pastes = db.Pastes.Where(p => p.ExpireDate != null && p.ExpireDate < curDate).ToList(); List<Paste> pastes = db.Pastes.Where(p => p.ExpireDate != null && p.ExpireDate < curDate).ToList();
var pasteStorageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig);
foreach (Paste paste in pastes) foreach (Paste paste in pastes)
{ {
string subDir = paste.FileName[0].ToString();
string filePath = Path.Combine(config.PasteConfig.PasteDirectory, subDir, paste.FileName);
// Delete the File // Delete the File
DeleteFile(filePath); DeleteFile(pasteStorageService, paste.FileName);
} }
db.RemoveRange(pastes); db.RemoveRange(pastes);
db.SaveChanges(); db.SaveChanges();
@ -283,37 +279,39 @@ namespace Teknik.ServiceWorker
public static void CleanUploadFiles(Config config, TeknikEntities db) public static void CleanUploadFiles(Config config, TeknikEntities db)
{ {
List<string> uploads = db.Uploads.Where(u => !string.IsNullOrEmpty(u.FileName)).Select(u => Path.Combine(config.UploadConfig.UploadDirectory, u.FileName[0].ToString(), u.FileName)).Select(u => u.ToLower()).ToList(); var storageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
List<string> files = Directory.GetFiles(config.UploadConfig.UploadDirectory, "*.*", SearchOption.AllDirectories).Select(f => f.ToLower()).ToList(); List<string> files = storageService.GetFileNames();
List<string> uploads = db.Uploads.Where(u => !string.IsNullOrEmpty(u.FileName)).Select(u => u.FileName.ToLower()).ToList();
var orphans = files.Except(uploads); var orphans = files.Except(uploads);
File.AppendAllLines(orphansFile, orphans); File.AppendAllLines(orphansFile, orphans);
foreach (var orphan in orphans) foreach (var orphan in orphans)
{ {
DeleteFile(orphan); DeleteFile(storageService, orphan);
} }
} }
public static void CleanPasteFiles(Config config, TeknikEntities db) public static void CleanPasteFiles(Config config, TeknikEntities db)
{ {
List<string> pastes = db.Pastes.Where(p => !string.IsNullOrEmpty(p.FileName)).Select(p => Path.Combine(config.PasteConfig.PasteDirectory, p.FileName[0].ToString(), p.FileName)).Select(p => p.ToLower()).ToList(); var storageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig);
List<string> files = Directory.GetFiles(config.PasteConfig.PasteDirectory, "*.*", SearchOption.AllDirectories).Select(f => f.ToLower()).ToList(); List<string> files = storageService.GetFileNames();
List<string> pastes = db.Pastes.Where(p => !string.IsNullOrEmpty(p.FileName)).Select(p => p.FileName.ToLower()).ToList();
var orphans = files.Except(pastes); var orphans = files.Except(pastes);
File.AppendAllLines(orphansFile, orphans); File.AppendAllLines(orphansFile, orphans);
foreach (var orphan in orphans) foreach (var orphan in orphans)
{ {
DeleteFile(orphan); DeleteFile(storageService, orphan);
} }
} }
public static void DeleteFile(string filePath) public static void DeleteFile(IStorageService storageService, string fileName)
{ {
try try
{ {
File.Delete(filePath); storageService.DeleteFile(fileName);
} }
catch (Exception ex) catch (Exception ex)
{ {
Output(string.Format("[{0}] Unable to delete file: {1} | {2}", DateTime.Now, filePath, ex.ToString())); Output(string.Format("[{0}] Unable to delete file: {1} | {2}", DateTime.Now, fileName, ex.ToString()));
} }
} }

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.IO;
using Teknik.Configuration;
namespace StorageService
{
public interface IStorageService
{
public string GetUniqueFileName();
public Stream GetFile(string fileName);
public List<string> GetFileNames();
public void SaveFile(string fileName, Stream file);
public void SaveEncryptedFile(string fileName, Stream file, int chunkSize, byte[] key, byte[] iv);
public void DeleteFile(string fileName);
}
}

View File

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.Utilities;
using Teknik.Utilities.Cryptography;
namespace StorageService
{
public class LocalStorageService : StorageService
{
public LocalStorageService(StorageConfig config) : base(config)
{
}
public override string GetUniqueFileName()
{
string filePath = FileHelper.GenerateRandomFileName(_config.LocalDirectory, _config.FileExtension, _config.FileNameLength);
return Path.GetFileName(filePath);
}
public override List<string> GetFileNames()
{
return Directory.GetFiles(_config.LocalDirectory, "*.*", SearchOption.AllDirectories).Select(f => Path.GetFileName(f).ToLower()).ToList();
}
public override Stream GetFile(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return null;
string filePath = GetFilePath(fileName);
if (File.Exists(filePath))
return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return null;
}
public override void SaveEncryptedFile(string fileName, Stream file, int chunkSize, byte[] key, byte[] iv)
{
if (!Directory.Exists(_config.LocalDirectory))
Directory.CreateDirectory(_config.LocalDirectory);
string filePath = GetFilePath(fileName);
AesCounterManaged.EncryptToFile(filePath, file, chunkSize, key, iv);
}
public override void SaveFile(string fileName, Stream file)
{
if (!Directory.Exists(_config.LocalDirectory))
Directory.CreateDirectory(_config.LocalDirectory);
string filePath = GetFilePath(fileName);
// Just write the stream to the file
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
file.Seek(0, SeekOrigin.Begin);
file.CopyTo(fileStream);
}
}
public override void DeleteFile(string fileName)
{
string filePath = GetFilePath(fileName);
// Delete the File
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
private string GetFilePath(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return null;
string subDir = fileName[0].ToString().ToLower();
return Path.Combine(_config.LocalDirectory, subDir, fileName);
}
}
}

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.IO;
using Teknik.Configuration;
namespace StorageService
{
public abstract class StorageService : IStorageService
{
protected readonly StorageConfig _config;
public StorageService(StorageConfig config)
{
_config = config;
}
public abstract string GetUniqueFileName();
public abstract Stream GetFile(string fileName);
public abstract List<string> GetFileNames();
public abstract void SaveFile(string fileName, Stream file);
public abstract void SaveEncryptedFile(string fileName, Stream file, int chunkSize, byte[] key, byte[] iv);
public abstract void DeleteFile(string fileName);
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Configuration\Configuration.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Teknik.Configuration;
namespace StorageService
{
public static class StorageServiceFactory
{
public static StorageService GetStorageService(StorageConfig config)
{
switch (config.Type)
{
case StorageType.Local:
return new LocalStorageService(config);
case StorageType.S3:
default:
return null;
}
}
}
}

View File

@ -32,7 +32,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityS
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContentScanningService", "ContentScanningService\ContentScanningService.csproj", "{491FE626-ABC8-4D00-8C7F-0849C357201A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ContentScanningService", "ContentScanningService\ContentScanningService.csproj", "{491FE626-ABC8-4D00-8C7F-0849C357201A}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebCommon", "WebCommon\WebCommon.csproj", "{32E85A7F-871A-437C-9BA3-00499AAB442C}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebCommon", "WebCommon\WebCommon.csproj", "{32E85A7F-871A-437C-9BA3-00499AAB442C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StorageService", "StorageService\StorageService.csproj", "{4A600C17-C772-462F-A37F-307E7893B2DB}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -107,6 +109,12 @@ Global
{32E85A7F-871A-437C-9BA3-00499AAB442C}.Release|Any CPU.Build.0 = Release|Any CPU {32E85A7F-871A-437C-9BA3-00499AAB442C}.Release|Any CPU.Build.0 = Release|Any CPU
{32E85A7F-871A-437C-9BA3-00499AAB442C}.Test|Any CPU.ActiveCfg = Debug|Any CPU {32E85A7F-871A-437C-9BA3-00499AAB442C}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{32E85A7F-871A-437C-9BA3-00499AAB442C}.Test|Any CPU.Build.0 = Debug|Any CPU {32E85A7F-871A-437C-9BA3-00499AAB442C}.Test|Any CPU.Build.0 = Debug|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Release|Any CPU.Build.0 = Release|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.ActiveCfg = Debug|Any CPU
{4A600C17-C772-462F-A37F-307E7893B2DB}.Test|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -22,6 +22,7 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Diagnostics;
using Teknik.Utilities.Routing; using Teknik.Utilities.Routing;
using StorageService;
namespace Teknik.Areas.Paste.Controllers namespace Teknik.Areas.Paste.Controllers
{ {
@ -58,7 +59,7 @@ namespace Teknik.Areas.Paste.Controllers
// Check Expiration // Check Expiration
if (PasteHelper.CheckExpiration(paste)) if (PasteHelper.CheckExpiration(paste))
{ {
DeleteFile(paste); PasteHelper.DeleteFile(_dbContext, _config, _logger, paste);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
@ -98,7 +99,7 @@ namespace Teknik.Areas.Paste.Controllers
if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword) if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword)
{ {
PasswordViewModel passModel = new PasswordViewModel(); PasswordViewModel passModel = new PasswordViewModel();
passModel.ActionUrl = Url.SubRouteUrl("p", "Paste.View"); passModel.ActionUrl = Url.SubRouteUrl("p", "Paste.View", new { type = type, url = url });
passModel.Url = url; passModel.Url = url;
passModel.Type = type; passModel.Type = type;
@ -119,15 +120,12 @@ namespace Teknik.Areas.Paste.Controllers
// Read in the file // Read in the file
if (string.IsNullOrEmpty(paste.FileName)) if (string.IsNullOrEmpty(paste.FileName))
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
string subDir = paste.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.PasteConfig.StorageConfig);
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName); var fileStream = storageService.GetFile(paste.FileName);
if (!System.IO.File.Exists(filePath)) if (fileStream == null)
{
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
}
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (AesCounterStream cs = new AesCounterStream(fileStream, false, keyBytes, ivBytes))
using (AesCounterStream cs = new AesCounterStream(fs, false, keyBytes, ivBytes))
using (StreamReader sr = new StreamReader(cs, Encoding.Unicode)) using (StreamReader sr = new StreamReader(cs, Encoding.Unicode))
{ {
model.Content = await sr.ReadToEndAsync(); model.Content = await sr.ReadToEndAsync();
@ -151,8 +149,7 @@ namespace Teknik.Areas.Paste.Controllers
Response.Headers.Add("Content-Disposition", cd.ToString()); Response.Headers.Add("Content-Disposition", cd.ToString());
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); return new BufferedFileStreamResult("application/octet-stream", async (response) => await ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fileStream, false, keyBytes, ivBytes), (int)fileStream.Length, _config.PasteConfig.ChunkSize), false);
return new BufferedFileStreamResult("application/octet-stream", async (response) => await ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fs, false, keyBytes, ivBytes), (int)fs.Length, _config.PasteConfig.ChunkSize), false);
default: default:
return View("~/Areas/Paste/Views/Paste/Full.cshtml", model); return View("~/Areas/Paste/Views/Paste/Full.cshtml", model);
} }
@ -220,7 +217,7 @@ namespace Teknik.Areas.Paste.Controllers
// Check Expiration // Check Expiration
if (PasteHelper.CheckExpiration(paste)) if (PasteHelper.CheckExpiration(paste))
{ {
DeleteFile(paste); PasteHelper.DeleteFile(_dbContext, _config, _logger, paste);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
@ -271,15 +268,12 @@ namespace Teknik.Areas.Paste.Controllers
// Read in the file // Read in the file
if (string.IsNullOrEmpty(paste.FileName)) if (string.IsNullOrEmpty(paste.FileName))
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
string subDir = paste.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.PasteConfig.StorageConfig);
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName); var fileStream = storageService.GetFile(paste.FileName);
if (!System.IO.File.Exists(filePath)) if (fileStream == null)
{
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
}
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (AesCounterStream cs = new AesCounterStream(fileStream, false, keyBytes, ivBytes))
using (AesCounterStream cs = new AesCounterStream(fs, false, keyBytes, ivBytes))
using (StreamReader sr = new StreamReader(cs, Encoding.Unicode)) using (StreamReader sr = new StreamReader(cs, Encoding.Unicode))
{ {
model.Content = await sr.ReadToEndAsync(); model.Content = await sr.ReadToEndAsync();
@ -333,17 +327,16 @@ namespace Teknik.Areas.Paste.Controllers
} }
// get the old file // get the old file
string subDir = paste.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.PasteConfig.StorageConfig);
string oldFile = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.FileName); var oldFile = paste.FileName;
// Generate a unique file name that does not currently exist // Generate a unique file name that does not currently exist
string newFilePath = FileHelper.GenerateRandomFileName(_config.PasteConfig.PasteDirectory, _config.PasteConfig.FileExtension, 10); string fileName = storageService.GetUniqueFileName();
string fileName = Path.GetFileName(newFilePath);
string key = PasteHelper.GenerateKey(_config.PasteConfig.KeySize); string key = PasteHelper.GenerateKey(_config.PasteConfig.KeySize);
string iv = PasteHelper.GenerateIV(_config.PasteConfig.BlockSize); string iv = PasteHelper.GenerateIV(_config.PasteConfig.BlockSize);
PasteHelper.EncryptContents(model.Content, newFilePath, password, key, iv, _config.PasteConfig.KeySize, _config.PasteConfig.ChunkSize); PasteHelper.EncryptContents(storageService, model.Content, fileName, password, key, iv, _config.PasteConfig.KeySize, _config.PasteConfig.ChunkSize);
paste.Key = key; paste.Key = key;
paste.KeySize = _config.PasteConfig.KeySize; paste.KeySize = _config.PasteConfig.KeySize;
@ -361,8 +354,7 @@ namespace Teknik.Areas.Paste.Controllers
_dbContext.SaveChanges(); _dbContext.SaveChanges();
// Delete the old file // Delete the old file
if (System.IO.File.Exists(oldFile)) storageService.DeleteFile(oldFile);
System.IO.File.Delete(oldFile);
return Redirect(Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url })); return Redirect(Url.SubRouteUrl("p", "Paste.View", new { type = "Full", url = paste.Url }));
} }
@ -385,7 +377,7 @@ namespace Teknik.Areas.Paste.Controllers
if (foundPaste.User.Username == User.Identity.Name || if (foundPaste.User.Username == User.Identity.Name ||
User.IsInRole("Admin")) User.IsInRole("Admin"))
{ {
DeleteFile(foundPaste); PasteHelper.DeleteFile(_dbContext, _config, _logger, foundPaste);
return Json(new { result = true, redirect = Url.SubRouteUrl("p", "Paste.Index") }); return Json(new { result = true, redirect = Url.SubRouteUrl("p", "Paste.Index") });
} }
@ -418,31 +410,5 @@ namespace Teknik.Areas.Paste.Controllers
HttpContext.Session.Remove("PastePassword_" + url); HttpContext.Session.Remove("PastePassword_" + url);
} }
} }
private void DeleteFile(Models.Paste paste)
{
if (!string.IsNullOrEmpty(paste.FileName))
{
string delSub = paste.FileName[0].ToString();
string delPath = Path.Combine(_config.PasteConfig.PasteDirectory, delSub, paste.FileName);
// Delete the File
if (System.IO.File.Exists(delPath))
{
try
{
System.IO.File.Delete(delPath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to delete file: {0}", paste.FileName);
}
}
}
// Delete from the DB
_dbContext.Pastes.Remove(paste);
_dbContext.SaveChanges();
}
} }
} }

View File

@ -9,6 +9,9 @@ using Teknik.Models;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
using Teknik.Data; using Teknik.Data;
using System.IO; using System.IO;
using StorageService;
using Teknik.Logging;
using Microsoft.Extensions.Logging;
namespace Teknik.Areas.Paste namespace Teknik.Areas.Paste
{ {
@ -56,15 +59,6 @@ namespace Teknik.Areas.Paste
break; break;
} }
if (!Directory.Exists(config.PasteConfig.PasteDirectory))
{
Directory.CreateDirectory(config.PasteConfig.PasteDirectory);
}
// Generate a unique file name that does not currently exist
string filePath = FileHelper.GenerateRandomFileName(config.PasteConfig.PasteDirectory, config.PasteConfig.FileExtension, 10);
string fileName = Path.GetFileName(filePath);
string key = GenerateKey(config.PasteConfig.KeySize); string key = GenerateKey(config.PasteConfig.KeySize);
string iv = GenerateIV(config.PasteConfig.BlockSize); string iv = GenerateIV(config.PasteConfig.BlockSize);
@ -73,8 +67,13 @@ namespace Teknik.Areas.Paste
paste.HashedPassword = HashPassword(key, password); paste.HashedPassword = HashPassword(key, password);
} }
// Generate a unique file name that does not currently exist
var storageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig);
var fileName = storageService.GetUniqueFileName();
// Encrypt the contents to the file // Encrypt the contents to the file
EncryptContents(content, filePath, password, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize); EncryptContents(storageService, content, fileName, password, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize);
// Generate a deletion key // Generate a deletion key
string delKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength); string delKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength);
@ -118,7 +117,7 @@ namespace Teknik.Areas.Paste
return SHA384.Hash(key, password).ToHex(); return SHA384.Hash(key, password).ToHex();
} }
public static void EncryptContents(string content, string filePath, string password, string key, string iv, int keySize, int chunkSize) public static void EncryptContents(IStorageService storageService, string content, string fileName, string password, string key, string iv, int keySize, int chunkSize)
{ {
byte[] ivBytes = Encoding.Unicode.GetBytes(iv); byte[] ivBytes = Encoding.Unicode.GetBytes(iv);
byte[] keyBytes = AesCounterManaged.CreateKey(key, ivBytes, keySize); byte[] keyBytes = AesCounterManaged.CreateKey(key, ivBytes, keySize);
@ -133,8 +132,25 @@ namespace Teknik.Areas.Paste
byte[] data = Encoding.Unicode.GetBytes(content); byte[] data = Encoding.Unicode.GetBytes(content);
using (MemoryStream ms = new MemoryStream(data)) using (MemoryStream ms = new MemoryStream(data))
{ {
AesCounterManaged.EncryptToFile(filePath, ms, chunkSize, keyBytes, ivBytes); storageService.SaveEncryptedFile(fileName, ms, chunkSize, keyBytes, ivBytes);
} }
} }
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, Models.Paste paste)
{
try
{
var storageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig);
storageService.DeleteFile(paste.FileName);
}
catch (Exception ex)
{
logger.LogError(ex, "Unable to delete file: {0}", paste.FileName);
}
// Delete from the DB
db.Pastes.Remove(paste);
db.SaveChanges();
}
} }
} }

View File

@ -24,6 +24,7 @@ using Teknik.Logging;
using Teknik.Areas.Users.Models; using Teknik.Areas.Users.Models;
using Teknik.ContentScanningService; using Teknik.ContentScanningService;
using Teknik.Utilities.Routing; using Teknik.Utilities.Routing;
using StorageService;
namespace Teknik.Areas.Upload.Controllers namespace Teknik.Areas.Upload.Controllers
{ {
@ -240,7 +241,7 @@ namespace Teknik.Areas.Upload.Controllers
// Check Expiration // Check Expiration
if (UploadHelper.CheckExpiration(upload)) if (UploadHelper.CheckExpiration(upload))
{ {
DeleteFile(upload); UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
@ -320,12 +321,12 @@ namespace Teknik.Areas.Upload.Controllers
} }
else else
{ {
string subDir = fileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.UploadConfig.StorageConfig);
string filePath = Path.Combine(_config.UploadConfig.UploadDirectory, subDir, fileName); var fileStream = storageService.GetFile(fileName);
long startByte = 0; long startByte = 0;
long endByte = contentLength - 1; long endByte = contentLength - 1;
long length = contentLength; long length = contentLength;
if (System.IO.File.Exists(filePath)) if (fileStream != null)
{ {
#region Range Calculation #region Range Calculation
// Are they downloading it by range? // Are they downloading it by range?
@ -413,11 +414,8 @@ namespace Teknik.Areas.Upload.Controllers
Response.Headers.Add("Content-Disposition", cd.ToString()); Response.Headers.Add("Content-Disposition", cd.ToString());
// Read in the file
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
// Reset file stream to starting position (or start of range) // Reset file stream to starting position (or start of range)
fs.Seek(startByte, SeekOrigin.Begin); fileStream.Seek(startByte, SeekOrigin.Begin);
try try
{ {
@ -427,12 +425,12 @@ namespace Teknik.Areas.Upload.Controllers
byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] keyBytes = Encoding.UTF8.GetBytes(key);
byte[] ivBytes = Encoding.UTF8.GetBytes(iv); byte[] ivBytes = Encoding.UTF8.GetBytes(iv);
return new BufferedFileStreamResult(contentType, async (response) => await ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fs, false, keyBytes, ivBytes), (int)length, _config.UploadConfig.ChunkSize), false); return new BufferedFileStreamResult(contentType, async (response) => await ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fileStream, false, keyBytes, ivBytes), (int)length, _config.UploadConfig.ChunkSize), false);
} }
else // Otherwise just send it else // Otherwise just send it
{ {
// Send the file // Send the file
return new BufferedFileStreamResult(contentType, async (response) => await ResponseHelper.StreamToOutput(response, true, fs, (int)length, _config.UploadConfig.ChunkSize), false); return new BufferedFileStreamResult(contentType, async (response) => await ResponseHelper.StreamToOutput(response, true, fileStream, (int)length, _config.UploadConfig.ChunkSize), false);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -459,13 +457,13 @@ namespace Teknik.Areas.Upload.Controllers
// Check Expiration // Check Expiration
if (UploadHelper.CheckExpiration(upload)) if (UploadHelper.CheckExpiration(upload))
{ {
DeleteFile(upload); UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
return Json(new { error = new { message = "File Does Not Exist" } }); return Json(new { error = new { message = "File Does Not Exist" } });
} }
string subDir = upload.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.UploadConfig.StorageConfig);
string filePath = Path.Combine(_config.UploadConfig.UploadDirectory, subDir, upload.FileName); var fileStream = storageService.GetFile(upload.FileName);
if (System.IO.File.Exists(filePath)) if (fileStream != null)
{ {
// Notify the client the content length we'll be outputting // Notify the client the content length we'll be outputting
Response.Headers.Add("Content-Length", upload.ContentLength.ToString()); Response.Headers.Add("Content-Length", upload.ContentLength.ToString());
@ -482,21 +480,18 @@ namespace Teknik.Areas.Upload.Controllers
Response.Headers.Add("Content-Disposition", cd.ToString()); Response.Headers.Add("Content-Disposition", cd.ToString());
// Read in the file
FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
// If the IV is set, and Key is set, then decrypt it while sending // If the IV is set, and Key is set, then decrypt it while sending
if (decrypt && !string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV)) if (decrypt && !string.IsNullOrEmpty(upload.Key) && !string.IsNullOrEmpty(upload.IV))
{ {
byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key); byte[] keyBytes = Encoding.UTF8.GetBytes(upload.Key);
byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV); byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV);
return new BufferedFileStreamResult(upload.ContentType, (response) => ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fs, false, keyBytes, ivBytes), (int)upload.ContentLength, _config.UploadConfig.ChunkSize), false); return new BufferedFileStreamResult(upload.ContentType, (response) => ResponseHelper.StreamToOutput(response, true, new AesCounterStream(fileStream, false, keyBytes, ivBytes), (int)upload.ContentLength, _config.UploadConfig.ChunkSize), false);
} }
else // Otherwise just send it else // Otherwise just send it
{ {
// Send the file // Send the file
return new BufferedFileStreamResult(upload.ContentType, (response) => ResponseHelper.StreamToOutput(response, true, fs, (int)upload.ContentLength, _config.UploadConfig.ChunkSize), false); return new BufferedFileStreamResult(upload.ContentType, (response) => ResponseHelper.StreamToOutput(response, true, fileStream, (int)upload.ContentLength, _config.UploadConfig.ChunkSize), false);
} }
} }
} }
@ -517,7 +512,7 @@ namespace Teknik.Areas.Upload.Controllers
model.File = file; model.File = file;
if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key) if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key)
{ {
DeleteFile(upload); UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
model.Deleted = true; model.Deleted = true;
} }
else else
@ -557,35 +552,12 @@ namespace Teknik.Areas.Upload.Controllers
{ {
if (foundUpload.User.Username == User.Identity.Name) if (foundUpload.User.Username == User.Identity.Name)
{ {
DeleteFile(foundUpload); UploadHelper.DeleteFile(_dbContext, _config, _logger, foundUpload);
return Json(new { result = true }); return Json(new { result = true });
} }
return Json(new { error = new { message = "You do not have permission to edit this Paste" } }); return Json(new { error = new { message = "You do not have permission to edit this Paste" } });
} }
return Json(new { error = new { message = "This Upload does not exist" } }); return Json(new { error = new { message = "This Upload does not exist" } });
} }
private void DeleteFile(Models.Upload upload)
{
string subDir = upload.FileName[0].ToString();
string filePath = Path.Combine(_config.UploadConfig.UploadDirectory, subDir, upload.FileName);
// Delete the File
if (System.IO.File.Exists(filePath))
{
try
{
System.IO.File.Delete(filePath);
}
catch (Exception ex)
{
_logger.LogError(ex, "Unable to delete file: {0}", upload.FileName);
}
}
// Delete from the DB
_dbContext.Uploads.Remove(upload);
_dbContext.SaveChanges();
}
} }
} }

View File

@ -9,6 +9,9 @@ using Teknik.Utilities;
using System.Text; using System.Text;
using Teknik.Utilities.Cryptography; using Teknik.Utilities.Cryptography;
using Teknik.Data; using Teknik.Data;
using StorageService;
using Teknik.Logging;
using Microsoft.Extensions.Logging;
namespace Teknik.Areas.Upload namespace Teknik.Areas.Upload
{ {
@ -36,14 +39,10 @@ namespace Teknik.Areas.Upload
public static Models.Upload SaveFile(TeknikEntities db, Config config, Stream file, string contentType, long contentLength, bool encrypt, ExpirationUnit expirationUnit, int expirationLength, string fileExt, string iv, string key, int keySize, int blockSize) public static Models.Upload SaveFile(TeknikEntities db, Config config, Stream file, string contentType, long contentLength, bool encrypt, ExpirationUnit expirationUnit, int expirationLength, string fileExt, string iv, string key, int keySize, int blockSize)
{ {
if (!Directory.Exists(config.UploadConfig.UploadDirectory)) var storageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
{
Directory.CreateDirectory(config.UploadConfig.UploadDirectory);
}
// Generate a unique file name that does not currently exist // Generate a unique file name that does not currently exist
string filePath = FileHelper.GenerateRandomFileName(config.UploadConfig.UploadDirectory, config.UploadConfig.FileExtension, 10); var fileName = storageService.GetUniqueFileName();
string fileName = Path.GetFileName(filePath);
// once we have the filename, lets save the file // once we have the filename, lets save the file
if (encrypt) if (encrypt)
@ -57,17 +56,11 @@ namespace Teknik.Areas.Upload
byte[] keyBytes = Encoding.UTF8.GetBytes(key); byte[] keyBytes = Encoding.UTF8.GetBytes(key);
byte[] ivBytes = Encoding.UTF8.GetBytes(iv); byte[] ivBytes = Encoding.UTF8.GetBytes(iv);
// Encrypt the file to disk storageService.SaveEncryptedFile(fileName, file, config.UploadConfig.ChunkSize, keyBytes, ivBytes);
AesCounterManaged.EncryptToFile(filePath, file, config.UploadConfig.ChunkSize, keyBytes, ivBytes);
} }
else else
{ {
// Just write the stream to the file storageService.SaveFile(fileName, file);
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
file.Seek(0, SeekOrigin.Begin);
file.CopyTo(fileStream);
}
} }
// Generate a unique url // Generate a unique url
@ -142,5 +135,22 @@ namespace Teknik.Areas.Upload
return upload; return upload;
} }
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, Models.Upload upload)
{
try
{
var storageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
storageService.DeleteFile(upload.FileName);
}
catch (Exception ex)
{
logger.LogError(ex, "Unable to delete file: {0}", upload.FileName);
}
// Delete from the DB
db.Uploads.Remove(upload);
db.SaveChanges();
}
} }
} }

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using StorageService;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@ -110,15 +111,14 @@ namespace Teknik.Areas.Vault.Controllers
if (!pasteModel.HasPassword) if (!pasteModel.HasPassword)
{ {
// Read in the file // Read in the file
string subDir = paste.Paste.FileName[0].ToString(); var storageService = StorageServiceFactory.GetStorageService(_config.PasteConfig.StorageConfig);
string filePath = Path.Combine(_config.PasteConfig.PasteDirectory, subDir, paste.Paste.FileName); var fileStream = storageService.GetFile(paste.Paste.FileName);
if (!System.IO.File.Exists(filePath)) if (fileStream == null)
continue; continue;
byte[] ivBytes = Encoding.Unicode.GetBytes(paste.Paste.IV); byte[] ivBytes = Encoding.Unicode.GetBytes(paste.Paste.IV);
byte[] keyBytes = AesCounterManaged.CreateKey(paste.Paste.Key, ivBytes, paste.Paste.KeySize); byte[] keyBytes = AesCounterManaged.CreateKey(paste.Paste.Key, ivBytes, paste.Paste.KeySize);
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) using (AesCounterStream cs = new AesCounterStream(fileStream, false, keyBytes, ivBytes))
using (AesCounterStream cs = new AesCounterStream(fs, false, keyBytes, ivBytes))
using (StreamReader sr = new StreamReader(cs, Encoding.Unicode)) using (StreamReader sr = new StreamReader(cs, Encoding.Unicode))
{ {
pasteModel.Content = await sr.ReadToEndAsync(); pasteModel.Content = await sr.ReadToEndAsync();

View File

@ -96,6 +96,7 @@
<ProjectReference Include="..\GitService\GitService.csproj" /> <ProjectReference Include="..\GitService\GitService.csproj" />
<ProjectReference Include="..\Logging\Logging.csproj" /> <ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\MailService\MailService.csproj" /> <ProjectReference Include="..\MailService\MailService.csproj" />
<ProjectReference Include="..\StorageService\StorageService.csproj" />
<ProjectReference Include="..\Tracking\Tracking.csproj" /> <ProjectReference Include="..\Tracking\Tracking.csproj" />
<ProjectReference Include="..\Utilities\Utilities.csproj" /> <ProjectReference Include="..\Utilities\Utilities.csproj" />
<ProjectReference Include="..\WebCommon\WebCommon.csproj" /> <ProjectReference Include="..\WebCommon\WebCommon.csproj" />

View File

@ -77,39 +77,45 @@ namespace Teknik.Utilities.Cryptography
} }
public static void EncryptToFile(string filePath, Stream input, int chunkSize, byte[] key, byte[] iv) public static void EncryptToFile(string filePath, Stream input, int chunkSize, byte[] key, byte[] iv)
{
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
EncryptToStream(input, fileStream, chunkSize, key, iv);
}
}
public static void EncryptToStream(Stream input, Stream output, int chunkSize, byte[] key, byte[] iv)
{ {
// Make sure the input stream is at the beginning // Make sure the input stream is at the beginning
input.Seek(0, SeekOrigin.Begin); input.Seek(0, SeekOrigin.Begin);
AesCounterStream cryptoStream = new AesCounterStream(input, true, key, iv); AesCounterStream cryptoStream = new AesCounterStream(input, true, key, iv);
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) int curByte = 0;
int processedBytes = 0;
byte[] buffer = new byte[chunkSize];
int bytesRemaining = (int)input.Length;
int bytesToRead = chunkSize;
do
{ {
int curByte = 0; if (chunkSize > bytesRemaining)
int processedBytes = 0;
byte[] buffer = new byte[chunkSize];
int bytesRemaining = (int)input.Length;
int bytesToRead = chunkSize;
do
{ {
if (chunkSize > bytesRemaining) bytesToRead = bytesRemaining;
{
bytesToRead = bytesRemaining;
}
processedBytes = cryptoStream.Read(buffer, 0, bytesToRead);
if (processedBytes > 0)
{
fileStream.Write(buffer, 0, processedBytes);
// Clear the buffer
Array.Clear(buffer, 0, chunkSize);
}
curByte += processedBytes;
bytesRemaining -= processedBytes;
} }
while (processedBytes > 0 && bytesRemaining > 0);
processedBytes = cryptoStream.Read(buffer, 0, bytesToRead);
if (processedBytes > 0)
{
output.Write(buffer, 0, processedBytes);
// Clear the buffer
Array.Clear(buffer, 0, chunkSize);
}
curByte += processedBytes;
bytesRemaining -= processedBytes;
} }
while (processedBytes > 0 && bytesRemaining > 0);
} }
public static byte[] CreateKey(string password, string iv, int keySize = 256) public static byte[] CreateKey(string password, string iv, int keySize = 256)