Browse Source

Uploads - Got encryption working, got file upload working (not encrypted version though), and got upload saving server side.

master
Teknikode 7 years ago
parent
commit
ebf069e43f
  1. 11
      Teknik/Areas/Upload/Controllers/UploadController.cs
  2. 10
      Teknik/Areas/Upload/Models/Upload.cs
  3. 23
      Teknik/Areas/Upload/Scripts/EncryptionWorker.js
  4. 139
      Teknik/Areas/Upload/Scripts/Upload.js
  5. 7
      Teknik/Areas/Upload/UploadAreaRegistration.cs
  6. 56
      Teknik/Areas/Upload/Uploader.cs
  7. 1
      Teknik/Areas/Upload/Views/Upload/Download.cshtml
  8. 5
      Teknik/Areas/Upload/Views/Upload/Index.cshtml
  9. 10
      Teknik/Configuration/UploadConfig.cs
  10. 65
      Teknik/Helpers/Utility.cs
  11. 3
      Teknik/Models/TeknikEntities.cs
  12. BIN
      Teknik/Scripts/_references.js
  13. 3
      Teknik/Teknik.csproj

11
Teknik/Areas/Upload/Controllers/UploadController.cs

@ -28,8 +28,9 @@ namespace Teknik.Areas.Upload.Controllers @@ -28,8 +28,9 @@ namespace Teknik.Areas.Upload.Controllers
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult Upload()
public ActionResult Upload(string iv)
{
Models.Upload upload = null;
foreach (string fileName in Request.Files)
{
HttpPostedFileBase file = Request.Files[fileName];
@ -37,9 +38,15 @@ namespace Teknik.Areas.Upload.Controllers @@ -37,9 +38,15 @@ namespace Teknik.Areas.Upload.Controllers
string fName = file.FileName;
if (file != null && file.ContentLength > 0)
{
upload = Uploader.SaveFile(file, iv);
break;
}
}
return Json(new { result = "tempURL.png" });
if (upload != null)
{
return Json(new { result = new { name = upload.Url, url = Url.SubRouteUrl("upload", "Upload.Download", new { file = upload.Url }) } }, "text/plain");
}
return Json(new { error = "Unable to upload file" });
}
[HttpPost]

10
Teknik/Areas/Upload/Models/Upload.cs

@ -18,9 +18,15 @@ namespace Teknik.Areas.Upload.Models @@ -18,9 +18,15 @@ namespace Teknik.Areas.Upload.Models
public string Url { get; set; }
public int FileSize { get; set; }
public string FileName { get; set; }
public string Hash { get; set; }
public int ContentLength { get; set; }
public string ContentType { get; set; }
public string Key { get; set; }
public string IV { get; set; }
public string DeleteKey { get; set; }
}

23
Teknik/Areas/Upload/Scripts/EncryptionWorker.js

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
self.addEventListener('message', function (e) {
var data = e.data;
importScripts(data.script);
switch (data.cmd) {
case 'encrypt':
// encrypt the passed in file data
var encrypted = CryptoJS.AES.encrypt(data.file, data.key, { iv: data.iv });
var cipherText = encrypted.toString();
self.postMessage(cipherText);
break;
case 'decrypt':
// decrypt the passed in file data
var decrypted = CryptoJS.AES.decrypt(data.file, data.key, { iv: data.iv });
var fileText = decrypted.toString();
self.postMessage(fileText);
break;
}
}, false);

139
Teknik/Areas/Upload/Scripts/Upload.js

@ -32,18 +32,19 @@ Dropzone.options.TeknikUpload = { @@ -32,18 +32,19 @@ Dropzone.options.TeknikUpload = {
paramName: "file", // The name that will be used to transfer the file
maxFilesize: maxUploadSize, // MB
addRemoveLinks: true,
autoProcessQueue: false,
autoProcessQueue: true,
clickable: true,
accept: function (file, done) {
file.done = done;
encryptFile(file);
encryptFile(file, done);
},
init: function() {
this.on("addedfile", function(file, responseText) {
$("#upload_message").css('display', 'none', 'important');
});
this.on("success", function(file, response) {
var name = response.result;
this.on("success", function (file, response) {
obj = JSON.parse(response);
var name = obj.result.name;
var fullName = obj.result.url;
var short_name = file.name.split(".")[0].hashCode();
$("#upload-links").css('display', 'inline', 'important');
$("#upload-links").prepend(' \
@ -52,7 +53,7 @@ Dropzone.options.TeknikUpload = { @@ -52,7 +53,7 @@ Dropzone.options.TeknikUpload = {
'+file.name+' \
</div> \
<div class="col-sm-3"> \
<a href='+uploadURL+'/'+name+'" target="_blank" class="alert-link">'+uploadURL+'/'+name+'</a> \
<a href="' + fullName + '" target="_blank" class="alert-link">' + fullName + '</a> \
</div> \
<div class="col-sm-3"> \
<button type="button" class="btn btn-default btn-xs generate-delete-link-'+short_name+'" id="'+name+'">Generate Deletion URL</button> \
@ -76,8 +77,8 @@ Dropzone.options.TeknikUpload = { @@ -76,8 +77,8 @@ Dropzone.options.TeknikUpload = {
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>'+errorMessage+'</div>');
});
this.on("totaluploadprogress", function(progress, totalBytes, totalBytesSent) {
$(".progress").children('.progress-bar').css('width', progress.toFixed(2)+'%');
$(".progress").children('.progress-bar').html(progress.toFixed(2)+'%');
$(".progress").children('.progress-bar').css('width', (progress.toFixed(2) * (3/5)) + 40 +'%');
$(".progress").children('.progress-bar').html(progress.toFixed(2)+'% Uploaded');
});
this.on("queuecomplete", function() {
$(".progress").children('.progress-bar').html('Complete');
@ -85,6 +86,91 @@ Dropzone.options.TeknikUpload = { @@ -85,6 +86,91 @@ Dropzone.options.TeknikUpload = {
}
};
// Function to encrypt a file, overide the file's data attribute with the encrypted value, and then call a callback function if supplied
function encryptFile(file, callback) {
// Start the file reader
var reader = new FileReader();
// When the file has been loaded, encrypt it
reader.onload = (function (theFile, callback) {
return function (e) {
// Create random key and iv
var keyStr = randomString(16, '#aA');
var ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
// Display encryption message
$(".progress").children('.progress-bar').css('width', '20%');
$(".progress").children('.progress-bar').html('Encrypting...');
var worker = new Worker(encScriptSrc);
worker.addEventListener('message', function (e) {
// create the blob from e.data.encrypted
theFile.data = e.data;
theFile.key = keyStr;
theFile.iv = ivStr;
$("#iv").val(ivStr);
if (callback != null) {
// Finish
callback();
}
});
// Execute worker with data
worker.postMessage({
cmd: 'encrypt',
script: aesScriptSrc,
key: key,
iv: iv,
file: e.target.result,
chunkSize: 1024
});
};
})(file, callback);
// While reading, display the current progress
reader.onprogress = function (data) {
if (data.lengthComputable) {
var progress = parseInt(((data.loaded / data.total) * 100), 10);
$(".progress").children('.progress-bar').css('width', (progress.toFixed(2) / 5) + '%');
$(".progress").children('.progress-bar').html(progress.toFixed(2) + '% Loaded');
}
}
// Start async read
reader.readAsDataURL(file);
}
function encryptData(data, file, callback) {
// 1) create the jQuery Deferred object that will be used
var deferred = $.Deferred();
// Create random key and iv
var keyStr = randomString(16, '#aA');
var ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
// Ecrypt the file
var encData = CryptoJS.AES.encrypt(data, key, { iv: iv });
// Save Data
file.data = encData;
file.key = keyStr;
file.iv = ivStr;
// Finish
callback();
// 2) return the promise of this deferred
return deferred.promise();
}
function readBlob(file, opt_startByte, opt_stopByte)
{
var start = parseInt(opt_startByte) || 0;
@ -93,10 +179,19 @@ function readBlob(file, opt_startByte, opt_stopByte) @@ -93,10 +179,19 @@ function readBlob(file, opt_startByte, opt_stopByte)
var reader = new FileReader();
reader.onload = (function (theFile) {
var callback = theFile.callback;
var callback = theFile.done;
window.processedSize += theFile.size;
return function (e) {
// Ecrypt the blog
window.bits.push(window.aesEncryptor.process(evt.target.result));
callback();
// Add the current size to the processed variable
window.processedSize += evt.target.result.size;
// If we have processed the entire file, let's do a finalize and call the callback
if (window.processedSize > window.totalSize - 1) {
window.bits.push(aesEncryptor.finalize());
callback();
}
};
})(file);
@ -109,29 +204,7 @@ function readBlob(file, opt_startByte, opt_stopByte) @@ -109,29 +204,7 @@ function readBlob(file, opt_startByte, opt_stopByte)
}
var blob = file.slice(start, stop + 1);
reader.readAsArrayBuffer(blob);
}
function encryptFile(file)
{
// INITIALIZE PROGRESSIVE ENCRYPTION
window.keyStr = randomString(16, '#aA');
window.ivStr = randomString(16, '#aA');
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
window.aesEncryptor = CryptoJS.algo.AES.createEncryptor(key, { iv: iv });
// LOOP THROUGH BYTES AND PROGRESSIVELY ENCRYPT
window.bits = []
var startByte = 0;
var endByte = 0;
while (startByte <= file.size - 1) {
endByte = startByte + 1024;
readBlob(file, startByte, endByte);
startByte = endByte;
}
reader.readAsDataURL(blob);
}
function fileDataEncrypted(done)

7
Teknik/Areas/Upload/UploadAreaRegistration.cs

@ -25,7 +25,7 @@ namespace Teknik.Areas.Upload @@ -25,7 +25,7 @@ namespace Teknik.Areas.Upload
context.MapSubdomainRoute(
"Upload.Download",
"dev",
"Upload/{url}",
"Upload/{file}",
new { controller = "Upload", action = "Download", url = string.Empty },
new[] { typeof(Controllers.UploadController).Namespace }
);
@ -106,6 +106,11 @@ namespace Teknik.Areas.Upload @@ -106,6 +106,11 @@ namespace Teknik.Areas.Upload
"~/Areas/Upload/Scripts/Upload.js",
"~/Scripts/bootbox/bootbox.min.js",
"~/Areas/Upload/Scripts/aes.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/cryptoWorker").Include(
"~/Areas/Upload/Scripts/EncryptionWorker.js"));
BundleTable.Bundles.Add(new ScriptBundle("~/bundles/crypto").Include(
"~/Areas/Upload/Scripts/aes.js"));
// Register Style Bundles
BundleTable.Bundles.Add(new StyleBundle("~/Content/upload").Include(
"~/Content/dropzone.css",

56
Teknik/Areas/Upload/Uploader.cs

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Teknik.Configuration;
using Teknik.Models;
namespace Teknik.Areas.Upload
{
public static class Uploader
{
public static Models.Upload SaveFile(HttpPostedFileBase file)
{
return SaveFile(file, null, null);
}
public static Models.Upload SaveFile(HttpPostedFileBase file, string iv)
{
return SaveFile(file, iv, null);
}
public static Models.Upload SaveFile(HttpPostedFileBase file, string iv, string key)
{
Config config = Config.Load();
TeknikEntities db = new TeknikEntities();
// Generate a unique file name that does not currently exist
string fileName = Utility.GenerateUniqueFileName(config.UploadConfig.UploadDirectory, config.UploadConfig.FileExtension, 10);
// once we have the filename, lets save the file
file.SaveAs(fileName);
// Generate a unique url
string extension = (config.UploadConfig.IncludeExtension) ? Utility.GetDefaultExtension(file.ContentType) : string.Empty;
string url = Utility.RandomString(config.UploadConfig.UrlLength) + extension;
while (db.Uploads.Where(u => u.Url == url).FirstOrDefault() != null)
{
url = Utility.RandomString(config.UploadConfig.UrlLength) + extension;
}
// Now we need to update the database with the new upload information
Models.Upload upload = db.Uploads.Create();
upload.DateUploaded = DateTime.Now;
upload.Url = url;
upload.FileName = fileName;
upload.ContentLength = file.ContentLength;
upload.ContentType = file.ContentType;
upload.Key = key;
upload.IV = iv;
db.Uploads.Add(upload);
return upload;
}
}
}

1
Teknik/Areas/Upload/Views/Upload/Download.cshtml

@ -0,0 +1 @@ @@ -0,0 +1 @@
@model Teknik.Areas.Upload.ViewModels.UploadViewModel

5
Teknik/Areas/Upload/Views/Upload/Index.cshtml

@ -1,6 +1,8 @@ @@ -1,6 +1,8 @@
@model Teknik.Areas.Upload.ViewModels.UploadViewModel
<script>
var encScriptSrc = '@Scripts.Url("~/bundles/cryptoWorker")';
var aesScriptSrc = '@Scripts.Url("~/bundles/crypto")';
var generateDeleteKeyURL = '@Url.SubRouteUrl("upload", "Upload.Action", new { action= "GenerateDeleteKey" })';
var uploadURL = '@Url.SubRouteUrl("upload", "Upload.Download")';
var maxUploadSize = @(Model.Config.UploadConfig.MaxUploadSize / 100000);
@ -10,8 +12,9 @@ @@ -10,8 +12,9 @@
<div class="container">
<div class="row text-center">
<form action="@Url.SubRouteUrl("upload", "Upload.Upload")" class="dropzone" id="TeknikUpload" name="TeknikUpload" enctype="multipart/form-data">
<form action="@Url.SubRouteUrl("upload", "Upload.Action", new { action = "Upload" })" class="dropzone" id="TeknikUpload" name="TeknikUpload" enctype="multipart/form-data">
@Html.AntiForgeryToken()
<input name="iv" id="iv" type="hidden" />
<div class="dz-message text-center" id="upload_message">
<div class="row">
<div class="col-sm-12">

10
Teknik/Configuration/UploadConfig.cs

@ -9,6 +9,12 @@ namespace Teknik.Configuration @@ -9,6 +9,12 @@ namespace Teknik.Configuration
{
// Max upload size in bytes
public int MaxUploadSize { 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 bool IncludeExtension { get; set; }
public UploadConfig()
{
@ -18,6 +24,10 @@ namespace Teknik.Configuration @@ -18,6 +24,10 @@ namespace Teknik.Configuration
public void SetDefaults()
{
MaxUploadSize = 100000000;
UploadDirectory = string.Empty;
FileExtension = "enc";
UrlLength = 6;
IncludeExtension = true;
}
}
}

65
Teknik/Helpers/Utility.cs

@ -1,7 +1,10 @@ @@ -1,7 +1,10 @@
using System;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
namespace Teknik
@ -25,5 +28,65 @@ namespace Teknik @@ -25,5 +28,65 @@ namespace Teknik
}
return result;
}
public static string RandomString(int length, string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{
const int byteSize = 0x100;
var allowedCharSet = new HashSet<char>(allowedChars).ToArray();
if (byteSize < allowedCharSet.Length) throw new ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize));
// Guid.NewGuid and System.Random are not particularly random. By using a
// cryptographically-secure random number generator, the caller is always
// protected, regardless of use.
using (var rng = new System.Security.Cryptography.RNGCryptoServiceProvider())
{
var result = new StringBuilder();
var buf = new byte[128];
while (result.Length < length)
{
rng.GetBytes(buf);
for (var i = 0; i < buf.Length && result.Length < length; ++i)
{
// Divide the byte into allowedCharSet-sized groups. If the
// random value falls into the last group and the last group is
// too small to choose from the entire allowedCharSet, ignore
// the value in order to avoid biasing the result.
var outOfRangeStart = byteSize - (byteSize % allowedCharSet.Length);
if (outOfRangeStart <= buf[i]) continue;
result.Append(allowedCharSet[buf[i] % allowedCharSet.Length]);
}
}
return result.ToString();
}
}
public static string GenerateUniqueFileName(string path, string extension, int length)
{
if (Directory.Exists(path))
{
string filename = RandomString(length);
while (File.Exists(Path.Combine(path, string.Format("{0}.{1}", filename, extension))))
{
filename = RandomString(length);
}
return Path.Combine(path, string.Format("{0}.{1}", filename, extension));
}
return string.Empty;
}
public static string GetDefaultExtension(string mimeType)
{
string result;
RegistryKey key;
object value;
key = Registry.ClassesRoot.OpenSubKey(@"MIME\Database\Content Type\" + mimeType, false);
value = key != null ? key.GetValue("Extension", null) : null;
result = value != null ? value.ToString() : string.Empty;
return result;
}
}
}

3
Teknik/Models/TeknikEntities.cs

@ -5,6 +5,7 @@ using Teknik.Areas.Blog.Models; @@ -5,6 +5,7 @@ using Teknik.Areas.Blog.Models;
using Teknik.Areas.Profile.Models;
using Teknik.Areas.Contact.Models;
using Teknik.Migrations;
using Teknik.Areas.Upload.Models;
namespace Teknik.Models
{
@ -17,6 +18,7 @@ namespace Teknik.Models @@ -17,6 +18,7 @@ namespace Teknik.Models
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> BlogComments { get; set; }
public DbSet<Contact> Contact { get; set; }
public DbSet<Upload> Uploads { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
@ -27,6 +29,7 @@ namespace Teknik.Models @@ -27,6 +29,7 @@ namespace Teknik.Models
modelBuilder.Entity<Post>().ToTable("Posts");
modelBuilder.Entity<Comment>().ToTable("BlogComments");
modelBuilder.Entity<Contact>().ToTable("Contact");
modelBuilder.Entity<Upload>().ToTable("Uploads");
base.OnModelCreating(modelBuilder);
}

BIN
Teknik/Scripts/_references.js

Binary file not shown.

3
Teknik/Teknik.csproj

@ -168,6 +168,7 @@ @@ -168,6 +168,7 @@
<Compile Include="Areas\Upload\Controllers\UploadController.cs" />
<Compile Include="Areas\Upload\Models\Upload.cs" />
<Compile Include="Areas\Upload\UploadAreaRegistration.cs" />
<Compile Include="Areas\Upload\Uploader.cs" />
<Compile Include="Areas\Upload\ViewModels\UploadViewModel.cs" />
<Compile Include="Configuration\BlogConfig.cs" />
<Compile Include="Configuration\Config.cs" />
@ -208,6 +209,7 @@ @@ -208,6 +209,7 @@
<Content Include="Areas\Home\Scripts\Home.js" />
<Content Include="Areas\Profile\Scripts\Profile.js" />
<Content Include="Areas\Upload\Scripts\aes.js" />
<Content Include="Areas\Upload\Scripts\EncryptionWorker.js" />
<Content Include="Areas\Upload\Scripts\Upload.js" />
<Content Include="Content\bootstrap-markdown.min.css" />
<Content Include="Content\bootstrap-theme.css" />
@ -278,6 +280,7 @@ @@ -278,6 +280,7 @@
<Content Include="Areas\Error\Views\Error\Exception.cshtml" />
<Content Include="Areas\Error\Views\Error\General.cshtml" />
<Content Include="Areas\Error\Views\Error\Http500.cshtml" />
<Content Include="Areas\Upload\Views\Upload\Download.cshtml" />
<None Include="Properties\PublishProfiles\Teknik Dev.pubxml" />
<None Include="Scripts\jquery-2.1.4.intellisense.js" />
<Content Include="Scripts\bootbox\bootbox.min.js" />

Loading…
Cancel
Save