diff --git a/Teknik/Areas/API/Controllers/APIv1Controller.cs b/Teknik/Areas/API/Controllers/APIv1Controller.cs index 6034c5d..8be5e21 100644 --- a/Teknik/Areas/API/Controllers/APIv1Controller.cs +++ b/Teknik/Areas/API/Controllers/APIv1Controller.cs @@ -44,31 +44,25 @@ namespace Teknik.Areas.API.Controllers if (model.file.ContentLength <= Config.UploadConfig.MaxUploadSize) { // convert file to bytes - byte[] fileData = null; string fileExt = Path.GetExtension(model.file.FileName); int contentLength = model.file.ContentLength; - using (var binaryReader = new BinaryReader(model.file.InputStream)) - { - fileData = binaryReader.ReadBytes(model.file.ContentLength); - } // Scan the file to detect a virus if (Config.UploadConfig.VirusScanEnable) { - byte[] scanData = fileData; // If it was encrypted client side, decrypt it - if (!model.encrypt && model.key != null) - { - // If the IV is set, and Key is set, then decrypt it - if (!string.IsNullOrEmpty(model.key) && !string.IsNullOrEmpty(model.iv)) - { - // Decrypt the data - scanData = AES.Decrypt(scanData, model.key, model.iv); - } - } + //if (!model.encrypt && model.key != null) + //{ + // // If the IV is set, and Key is set, then decrypt it + // if (!string.IsNullOrEmpty(model.key) && !string.IsNullOrEmpty(model.iv)) + // { + // // Decrypt the data + // scanData = AES.Decrypt(scanData, model.key, model.iv); + // } + //} ClamClient clam = new ClamClient(Config.UploadConfig.ClamServer, Config.UploadConfig.ClamPort); clam.MaxStreamSize = Config.UploadConfig.MaxUploadSize; - ClamScanResult scanResult = clam.SendAndScanFile(scanData); + ClamScanResult scanResult = clam.SendAndScanFile(model.file.InputStream); switch (scanResult.Result) { @@ -95,29 +89,8 @@ namespace Teknik.Areas.API.Controllers if (model.blockSize <= 0) model.blockSize = Config.UploadConfig.BlockSize; - byte[] data = null; - // If they want us to encrypt the file first, do that here - if (model.encrypt) - { - // Generate key and iv if empty - if (string.IsNullOrEmpty(model.key)) - { - model.key = StringHelper.RandomString(model.keySize / 8); - } - if (string.IsNullOrEmpty(model.iv)) - { - model.iv = StringHelper.RandomString(model.blockSize / 8); - } - - data = AES.Encrypt(fileData, model.key, model.iv); - if (data == null || data.Length <= 0) - { - return Json(new { error = new { message = "Unable to encrypt file" } }); - } - } - // Save the file data - Upload.Models.Upload upload = Uploader.SaveFile(db, Config, (model.encrypt) ? data : fileData, model.contentType, contentLength, fileExt, model.iv, (model.saveKey) ? model.key : null, model.keySize, model.blockSize); + Upload.Models.Upload upload = Uploader.SaveFile(db, Config, model.file.InputStream, model.contentType, contentLength, model.encrypt, fileExt, model.iv, model.key, model.saveKey, model.keySize, model.blockSize); if (upload != null) { diff --git a/Teknik/Areas/Upload/Controllers/UploadController.cs b/Teknik/Areas/Upload/Controllers/UploadController.cs index 04c41c0..7f8f7fa 100644 --- a/Teknik/Areas/Upload/Controllers/UploadController.cs +++ b/Teknik/Areas/Upload/Controllers/UploadController.cs @@ -60,30 +60,24 @@ namespace Teknik.Areas.Upload.Controllers if (data.ContentLength <= Config.UploadConfig.MaxUploadSize) { // convert file to bytes - byte[] fileData = null; int contentLength = data.ContentLength; - using (var binaryReader = new BinaryReader(data.InputStream)) - { - fileData = binaryReader.ReadBytes(data.ContentLength); - } // Scan the file to detect a virus if (Config.UploadConfig.VirusScanEnable) { - byte[] scanData = fileData; // If it was encrypted client side, decrypt it - if (!encrypt && key != null) - { - // If the IV is set, and Key is set, then decrypt it - if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(iv)) - { - // Decrypt the data - scanData = AES.Decrypt(scanData, key, iv); - } - } + //if (!encrypt && key != null) + //{ + // // If the IV is set, and Key is set, then decrypt it + // if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(iv)) + // { + // // Decrypt the data + // scanData = AES.Decrypt(scanData, key, iv); + // } + //} ClamClient clam = new ClamClient(Config.UploadConfig.ClamServer, Config.UploadConfig.ClamPort); clam.MaxStreamSize = Config.UploadConfig.MaxUploadSize; - ClamScanResult scanResult = clam.SendAndScanFile(scanData); + ClamScanResult scanResult = clam.SendAndScanFile(data.InputStream); switch (scanResult.Result) { @@ -97,23 +91,8 @@ namespace Teknik.Areas.Upload.Controllers return Json(new { error = new { message = string.Format("Unknown result while scanning the file upload for viruses. {0}", scanResult.RawResult) } }); } } - - // if they want us to encrypt it, we do so here - if (encrypt) - { - // Generate key and iv if empty - if (string.IsNullOrEmpty(key)) - { - key = StringHelper.RandomString(keySize / 8); - } - - fileData = AES.Encrypt(fileData, key, iv); - if (fileData == null || fileData.Length <= 0) - { - return Json(new { error = new { message = "Unable to encrypt file" } }); - } - } - Models.Upload upload = Uploader.SaveFile(db, Config, fileData, fileType, contentLength, fileExt, iv, (saveKey) ? key : null, keySize, blockSize); + + Models.Upload upload = Uploader.SaveFile(db, Config, data.InputStream, fileType, contentLength, encrypt, fileExt, iv, key, saveKey, keySize, blockSize); if (upload != null) { if (User.Identity.IsAuthenticated) diff --git a/Teknik/Areas/Upload/Uploader.cs b/Teknik/Areas/Upload/Uploader.cs index f17c456..58213b6 100644 --- a/Teknik/Areas/Upload/Uploader.cs +++ b/Teknik/Areas/Upload/Uploader.cs @@ -6,31 +6,32 @@ using System.IO; using Teknik.Configuration; using Teknik.Models; using Teknik.Utilities; +using System.Text; namespace Teknik.Areas.Upload { public static class Uploader { - public static Models.Upload SaveFile(TeknikEntities db, Config config, byte[] file, string contentType, int contentLength) + public static Models.Upload SaveFile(TeknikEntities db, Config config, System.IO.Stream file, string contentType, int contentLength, bool encrypt) { - return SaveFile(db, config, file, contentType, contentLength, string.Empty, null, null, 256, 128); + return SaveFile(db, config, file, contentType, contentLength, encrypt, string.Empty, null, null, false, 256, 128); } - public static Models.Upload SaveFile(TeknikEntities db, Config config, byte[] file, string contentType, int contentLength, string defaultExtension) + public static Models.Upload SaveFile(TeknikEntities db, Config config, System.IO.Stream file, string contentType, int contentLength, bool encrypt, string defaultExtension) { - return SaveFile(db, config, file, contentType, contentLength, defaultExtension, null, null, 256, 128); + return SaveFile(db, config, file, contentType, contentLength, encrypt, defaultExtension, null, null, false, 256, 128); } - public static Models.Upload SaveFile(TeknikEntities db, Config config, byte[] file, string contentType, int contentLength, string defaultExtension, string iv) + public static Models.Upload SaveFile(TeknikEntities db, Config config, System.IO.Stream file, string contentType, int contentLength, bool encrypt, string defaultExtension, string iv) { - return SaveFile(db, config, file, contentType, contentLength, defaultExtension, iv, null, 256, 128); + return SaveFile(db, config, file, contentType, contentLength, encrypt, defaultExtension, iv, null, false, 256, 128); } - public static Models.Upload SaveFile(TeknikEntities db, Config config, byte[] file, string contentType, int contentLength, string defaultExtension, string iv, string key) + public static Models.Upload SaveFile(TeknikEntities db, Config config, System.IO.Stream file, string contentType, int contentLength, bool encrypt, string defaultExtension, string iv, string key, bool saveKey) { - return SaveFile(db, config, file, contentType, contentLength, defaultExtension, iv, key, 256, 128); + return SaveFile(db, config, file, contentType, contentLength, encrypt, defaultExtension, iv, key, saveKey, 256, 128); } - public static Models.Upload SaveFile(TeknikEntities db, Config config, byte[] file, string contentType, int contentLength, string defaultExtension, string iv, string key, int keySize, int blockSize) + public static Models.Upload SaveFile(TeknikEntities db, Config config, System.IO.Stream file, string contentType, int contentLength, bool encrypt, string defaultExtension, string iv, string key, bool saveKey, int keySize, int blockSize) { if (!Directory.Exists(config.UploadConfig.UploadDirectory)) { @@ -42,7 +43,33 @@ namespace Teknik.Areas.Upload string fileName = Path.GetFileName(filePath); // once we have the filename, lets save the file - File.WriteAllBytes(filePath, file); + if (encrypt) + { + // Generate key and iv if empty + if (string.IsNullOrEmpty(key)) + { + key = StringHelper.RandomString(keySize / 8); + } + if (string.IsNullOrEmpty(iv)) + { + iv = StringHelper.RandomString(blockSize / 8); + } + + byte[] keyBytes = Encoding.UTF8.GetBytes(key); + byte[] ivBytes = Encoding.UTF8.GetBytes(iv); + + // Encrypt the file to disk + AES.EncryptToFile(filePath, file, config.UploadConfig.ChunkSize, keyBytes, ivBytes, "CTR", "NoPadding"); + } + else + { + // 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); + } + } // Generate a unique url string extension = (config.UploadConfig.IncludeExtension) ? FileHelper.GetDefaultExtension(contentType, defaultExtension) : string.Empty; @@ -59,7 +86,7 @@ namespace Teknik.Areas.Upload upload.FileName = fileName; upload.ContentType = (!string.IsNullOrEmpty(contentType)) ? contentType : "application/octet-stream"; upload.ContentLength = contentLength; - upload.Key = key; + upload.Key = (saveKey) ? key : null; upload.IV = iv; upload.KeySize = keySize; upload.BlockSize = blockSize; diff --git a/Utilities/Utilities/Crypto.cs b/Utilities/Utilities/Crypto.cs index fcd6393..72ae54b 100644 --- a/Utilities/Utilities/Crypto.cs +++ b/Utilities/Utilities/Crypto.cs @@ -155,19 +155,12 @@ namespace Teknik.Utilities byte[] ivBytes = Encoding.UTF8.GetBytes(iv); return Decrypt(data, keyBytes, ivBytes, "CTR", "NoPadding"); } - public static byte[] DecryptCBC(byte[] data, string key, string iv) - { - byte[] keyBytes = Encoding.UTF8.GetBytes(key); - byte[] ivBytes = Encoding.UTF8.GetBytes(iv); - return Decrypt(data, keyBytes, ivBytes, "CBC", "PKCS5PADDING"); - } public static byte[] Decrypt(byte[] data, byte[] key, byte[] iv, string mode, string padding) { - IBufferedCipher cipher = CipherUtilities.GetCipher("AES/" + mode + "/" + padding); - - cipher.Init(false, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", key), iv)); - - return cipher.DoFinal(data); + using (MemoryStream stream = new MemoryStream(data)) + { + return ProcessCipher(false, stream, 1024, key, iv, mode, padding); + } } @@ -181,19 +174,96 @@ namespace Teknik.Utilities byte[] ivBytes = Encoding.UTF8.GetBytes(iv); return Encrypt(data, keyBytes, ivBytes, "CTR", "NoPadding"); } - public static byte[] EncryptCBC(byte[] data, string key, string iv) - { - byte[] keyBytes = Encoding.UTF8.GetBytes(key); - byte[] ivBytes = Encoding.UTF8.GetBytes(iv); - return Encrypt(data, keyBytes, ivBytes, "CBC", "PKCS5PADDING"); - } public static byte[] Encrypt(byte[] data, byte[] key, byte[] iv, string mode, string padding) + { + using (MemoryStream stream = new MemoryStream(data)) + { + return ProcessCipher(true, stream, 1024, key, iv, mode, padding); + } + } + + public static byte[] ProcessCipher(bool encrypt, Stream input, int blockSize, byte[] key, byte[] iv, string mode, string padding) + { + IBufferedCipher cipher = CipherUtilities.GetCipher("AES/" + mode + "/" + padding); + + cipher.Init(encrypt, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", key), iv)); + + // Make sure the input stream is at the beginning + input.Seek(0, SeekOrigin.Begin); + + // Initialize variables + byte[] output = new byte[input.Length]; + int cipherOffset = 0; + int processedBytes = 0; + + // Process the stream and save the bytes to the output + do + { + processedBytes = ProcessCipherBlock(cipher, input, blockSize, output, cipherOffset); + cipherOffset += processedBytes; + } + while (processedBytes > 0); + + // Finalize processing of the cipher + cipher.DoFinal(output, cipherOffset); + + return output; + } + + public static void EncryptToFile(string filePath, Stream input, int blockSize, byte[] key, byte[] iv, string mode, string padding) { IBufferedCipher cipher = CipherUtilities.GetCipher("AES/" + mode + "/" + padding); cipher.Init(true, new ParametersWithIV(ParameterUtilities.CreateKeyParameter("AES", key), iv)); - return cipher.DoFinal(data); + // Make sure the input stream is at the beginning + input.Seek(0, SeekOrigin.Begin); + + using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write)) + { + int processedBytes = 0; + byte[] buffer = new byte[blockSize]; + do + { + processedBytes = ProcessCipherBlock(cipher, input, blockSize, buffer, 0); + if (processedBytes > 0) + { + // We have bytes, lets write them to the file + fileStream.Write(buffer, 0, processedBytes); + + // Clear the buffer + Array.Clear(buffer, 0, blockSize); + } + } + while (processedBytes > 0); + + // Clear the buffer + Array.Clear(buffer, 0, blockSize); + + // Do the final output + processedBytes = cipher.DoFinal(buffer, 0); + if (processedBytes > 0) + { + // We have bytes, lets write them to the file + fileStream.Write(buffer, 0, processedBytes); + } + } + } + + public static int ProcessCipherBlock(IBufferedCipher cipher, Stream input, int blockSize, byte[] output, int outputOffset) + { + // Initialize buffer + byte[] buffer = new byte[blockSize]; + + // Read the next block of data + int bytesRead = input.Read(buffer, 0, blockSize); + if (bytesRead > 0) + { + // process the cipher for the read block and add it to the output + return cipher.ProcessBytes(buffer, 0, bytesRead, output, outputOffset); + } + + return 0; } public static byte[] CreateKey(string password, string iv, int keySize = 256)