mirror of
https://git.teknik.io/Teknikode/Teknik.git
synced 2023-08-02 14:16:22 +02:00
Added scanning based on sha1 hash of the file to an endpoint
This commit is contained in:
parent
2b14547c25
commit
c701493859
21
Configuration/ClamConfig.cs
Normal file
21
Configuration/ClamConfig.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Teknik.Configuration
|
||||||
|
{
|
||||||
|
public class ClamConfig
|
||||||
|
{
|
||||||
|
// Virus Scanning Settings
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string Server { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
|
||||||
|
public ClamConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
Server = "localhost";
|
||||||
|
Port = 3310;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
Configuration/HashScanConfig.cs
Normal file
24
Configuration/HashScanConfig.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Teknik.Configuration
|
||||||
|
{
|
||||||
|
public class HashScanConfig
|
||||||
|
{
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
public bool Authenticate { get; set; }
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
public HashScanConfig()
|
||||||
|
{
|
||||||
|
Enabled = false;
|
||||||
|
Endpoint = string.Empty;
|
||||||
|
Authenticate = false;
|
||||||
|
Username = string.Empty;
|
||||||
|
Password = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,9 +31,9 @@ namespace Teknik.Configuration
|
|||||||
// 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; }
|
||||||
// Virus Scanning Settings
|
// Virus Scanning Settings
|
||||||
public bool VirusScanEnable { get; set; }
|
public ClamConfig ClamConfig { get; set; }
|
||||||
public string ClamServer { get; set; }
|
// Hash Scanning Settings
|
||||||
public int ClamPort { get; set; }
|
public HashScanConfig HashScanConfig { 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; }
|
||||||
@ -61,9 +61,8 @@ namespace Teknik.Configuration
|
|||||||
BlockSize = 128;
|
BlockSize = 128;
|
||||||
IncludeExtension = true;
|
IncludeExtension = true;
|
||||||
ChunkSize = 1024;
|
ChunkSize = 1024;
|
||||||
VirusScanEnable = false;
|
ClamConfig = new ClamConfig();
|
||||||
ClamServer = "localhost";
|
HashScanConfig = new HashScanConfig();
|
||||||
ClamPort = 3310;
|
|
||||||
RestrictedContentTypes = new List<string>();
|
RestrictedContentTypes = new List<string>();
|
||||||
RestrictedExtensions = new List<string>();
|
RestrictedExtensions = new List<string>();
|
||||||
}
|
}
|
||||||
|
50
ContentScanningService/ClamScanner.cs
Normal file
50
ContentScanningService/ClamScanner.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using nClam;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Teknik.Configuration;
|
||||||
|
|
||||||
|
namespace Teknik.ContentScanningService
|
||||||
|
{
|
||||||
|
public class ClamScanner : ContentScanner
|
||||||
|
{
|
||||||
|
public ClamScanner(Config config) : base(config)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async override Task<ScanResult> ScanFile(Stream stream)
|
||||||
|
{
|
||||||
|
var result = new ScanResult();
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
// Set the start of the stream
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
ClamClient clam = new ClamClient(_config.UploadConfig.ClamConfig.Server, _config.UploadConfig.ClamConfig.Port);
|
||||||
|
clam.MaxStreamSize = stream.Length;
|
||||||
|
ClamScanResult scanResult = await clam.SendAndScanFileAsync(stream);
|
||||||
|
|
||||||
|
result.RawResult = scanResult.RawResult;
|
||||||
|
switch (scanResult.Result)
|
||||||
|
{
|
||||||
|
case ClamScanResults.Clean:
|
||||||
|
result.ResultType = ScanResultType.Clean;
|
||||||
|
break;
|
||||||
|
case ClamScanResults.VirusDetected:
|
||||||
|
result.ResultType = ScanResultType.VirusDetected;
|
||||||
|
result.RawResult = scanResult.InfectedFiles.First().VirusName;
|
||||||
|
break;
|
||||||
|
case ClamScanResults.Error:
|
||||||
|
result.ResultType = ScanResultType.Error;
|
||||||
|
break;
|
||||||
|
case ClamScanResults.Unknown:
|
||||||
|
result.ResultType = ScanResultType.Unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
ContentScanningService/ContentScanner.cs
Normal file
21
ContentScanningService/ContentScanner.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Teknik.Configuration;
|
||||||
|
|
||||||
|
namespace Teknik.ContentScanningService
|
||||||
|
{
|
||||||
|
public abstract class ContentScanner
|
||||||
|
{
|
||||||
|
protected readonly Config _config;
|
||||||
|
|
||||||
|
public ContentScanner(Config config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task<ScanResult> ScanFile(Stream stream);
|
||||||
|
}
|
||||||
|
}
|
19
ContentScanningService/ContentScanningService.csproj
Normal file
19
ContentScanningService/ContentScanningService.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||||
|
<AssemblyName>Teknik.ContentScanningService</AssemblyName>
|
||||||
|
<RootNamespace>Teknik.ContentScanningService</RootNamespace>
|
||||||
|
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;linux-arm;osx-x64</RuntimeIdentifiers>
|
||||||
|
<Configurations>Debug;Release;Test</Configurations>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="nClam" Version="4.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Configuration\Configuration.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
55
ContentScanningService/HashScanner.cs
Normal file
55
ContentScanningService/HashScanner.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Teknik.Configuration;
|
||||||
|
using Teknik.Utilities.Cryptography;
|
||||||
|
|
||||||
|
namespace Teknik.ContentScanningService
|
||||||
|
{
|
||||||
|
public class HashScanner : ContentScanner
|
||||||
|
{
|
||||||
|
private static readonly HttpClient _client = new HttpClient();
|
||||||
|
public HashScanner(Config config) : base(config)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public async override Task<ScanResult> ScanFile(Stream stream)
|
||||||
|
{
|
||||||
|
var result = new ScanResult();
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
// Set the start of the stream
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
if (_config.UploadConfig.HashScanConfig.Authenticate)
|
||||||
|
{
|
||||||
|
var byteArray = Encoding.UTF8.GetBytes($"{_config.UploadConfig.HashScanConfig.Username}:{_config.UploadConfig.HashScanConfig.Password}");
|
||||||
|
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the hash of the stream
|
||||||
|
var hash = Utilities.Cryptography.SHA1.Hash(stream);
|
||||||
|
|
||||||
|
HttpResponseMessage response = await _client.PostAsync(_config.UploadConfig.HashScanConfig.Endpoint, new StringContent(hash));
|
||||||
|
HttpContent content = response.Content;
|
||||||
|
|
||||||
|
string resultStr = await content.ReadAsStringAsync();
|
||||||
|
if (resultStr == "true")
|
||||||
|
{
|
||||||
|
// The hash matched a CP entry, let's return the result as such
|
||||||
|
result.ResultType = ScanResultType.ChildPornography;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.RawResult = resultStr + " | " + hash + _client.DefaultRequestHeaders.Authorization.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
ContentScanningService/ScanResult.cs
Normal file
19
ContentScanningService/ScanResult.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Teknik.ContentScanningService
|
||||||
|
{
|
||||||
|
public class ScanResult
|
||||||
|
{
|
||||||
|
public string RawResult { get; set;}
|
||||||
|
public ScanResultType ResultType { get; set; }
|
||||||
|
|
||||||
|
public ScanResult()
|
||||||
|
{
|
||||||
|
RawResult = string.Empty;
|
||||||
|
ResultType = ScanResultType.Clean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
ContentScanningService/ScanResultType.cs
Normal file
15
ContentScanningService/ScanResultType.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Teknik.ContentScanningService
|
||||||
|
{
|
||||||
|
public enum ScanResultType
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Clean = 1,
|
||||||
|
VirusDetected = 2,
|
||||||
|
ChildPornography = 3,
|
||||||
|
Error = 4
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceWorker", "ServiceWor
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{3434645B-B8B4-457A-8C85-342E6727CCEE}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer", "IdentityServer\IdentityServer.csproj", "{3434645B-B8B4-457A-8C85-342E6727CCEE}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContentScanningService", "ContentScanningService\ContentScanningService.csproj", "{491FE626-ABC8-4D00-8C7F-0849C357201A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -92,6 +94,12 @@ Global
|
|||||||
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Test|Any CPU.ActiveCfg = Test|Any CPU
|
||||||
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Test|Any CPU.Build.0 = Test|Any CPU
|
{3434645B-B8B4-457A-8C85-342E6727CCEE}.Test|Any CPU.Build.0 = Test|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Test|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{491FE626-ABC8-4D00-8C7F-0849C357201A}.Test|Any CPU.Build.0 = Debug|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -16,6 +16,7 @@ using Teknik.Areas.Upload;
|
|||||||
using Teknik.Areas.Users.Models;
|
using Teknik.Areas.Users.Models;
|
||||||
using Teknik.Areas.Users.Utility;
|
using Teknik.Areas.Users.Utility;
|
||||||
using Teknik.Configuration;
|
using Teknik.Configuration;
|
||||||
|
using Teknik.ContentScanningService;
|
||||||
using Teknik.Data;
|
using Teknik.Data;
|
||||||
using Teknik.Filters;
|
using Teknik.Filters;
|
||||||
using Teknik.Logging;
|
using Teknik.Logging;
|
||||||
@ -29,7 +30,6 @@ namespace Teknik.Areas.API.V1.Controllers
|
|||||||
public UploadAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
|
public UploadAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
|
||||||
[TrackPageView]
|
[TrackPageView]
|
||||||
public async Task<IActionResult> Upload(UploadAPIv1Model model)
|
public async Task<IActionResult> Upload(UploadAPIv1Model model)
|
||||||
{
|
{
|
||||||
@ -73,37 +73,47 @@ namespace Teknik.Areas.API.V1.Controllers
|
|||||||
string fileExt = FileHelper.GetFileExtension(model.file.FileName);
|
string fileExt = FileHelper.GetFileExtension(model.file.FileName);
|
||||||
long contentLength = model.file.Length;
|
long contentLength = model.file.Length;
|
||||||
|
|
||||||
// Scan the file to detect a virus
|
using (var fs = model.file.OpenReadStream())
|
||||||
if (_config.UploadConfig.VirusScanEnable)
|
|
||||||
{
|
{
|
||||||
ClamClient clam = new ClamClient(_config.UploadConfig.ClamServer, _config.UploadConfig.ClamPort);
|
ScanResult scanResult = null;
|
||||||
clam.MaxStreamSize = maxUploadSize;
|
|
||||||
ClamScanResult scanResult = await clam.SendAndScanFileAsync(model.file.OpenReadStream());
|
|
||||||
|
|
||||||
switch (scanResult.Result)
|
// Scan the file to detect a virus
|
||||||
|
if (_config.UploadConfig.ClamConfig.Enabled)
|
||||||
{
|
{
|
||||||
case ClamScanResults.Clean:
|
var clamScanner = new ClamScanner(_config);
|
||||||
break;
|
scanResult = await clamScanner.ScanFile(fs);
|
||||||
case ClamScanResults.VirusDetected:
|
|
||||||
return Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.InfectedFiles.First().VirusName, Url.SubRouteUrl("tos", "TOS.Index")) } });
|
|
||||||
case ClamScanResults.Error:
|
|
||||||
break;
|
|
||||||
case ClamScanResults.Unknown:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Need to grab the contentType if it's empty
|
// Scan the files against an endpoint based on hash
|
||||||
if (string.IsNullOrEmpty(model.contentType))
|
if (_config.UploadConfig.HashScanConfig.Enabled && (scanResult == null || scanResult.ResultType == ScanResultType.Clean))
|
||||||
{
|
{
|
||||||
model.contentType = model.file.ContentType;
|
var hashScanner = new HashScanner(_config);
|
||||||
|
scanResult = await hashScanner.ScanFile(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (scanResult?.ResultType)
|
||||||
|
{
|
||||||
|
case ScanResultType.Clean:
|
||||||
|
break;
|
||||||
|
case ScanResultType.VirusDetected:
|
||||||
|
return Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.RawResult, Url.SubRouteUrl("tos", "TOS.Index")) } });
|
||||||
|
case ScanResultType.ChildPornography:
|
||||||
|
return Json(new { error = new { message = string.Format("Child Pornography Detected: As per our <a href=\"{0}\">Terms of Service</a>, Child Pornography is strictly not permited.", Url.SubRouteUrl("tos", "TOS.Index")) } });
|
||||||
|
case ScanResultType.Error:
|
||||||
|
return Json(new { error = new { message = string.Format("Error scanning the file upload. {0}", scanResult.RawResult) } });
|
||||||
|
case ScanResultType.Unknown:
|
||||||
|
return Json(new { error = new { message = string.Format("Unknown result while scanning the file upload. {0}", scanResult.RawResult) } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to grab the contentType if it's empty
|
||||||
if (string.IsNullOrEmpty(model.contentType))
|
if (string.IsNullOrEmpty(model.contentType))
|
||||||
{
|
{
|
||||||
using (Stream fileStream = model.file.OpenReadStream())
|
model.contentType = model.file.ContentType;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(model.contentType))
|
||||||
{
|
{
|
||||||
fileStream.Seek(0, SeekOrigin.Begin);
|
fs.Seek(0, SeekOrigin.Begin);
|
||||||
FileType fileType = fileStream.GetFileType();
|
FileType fileType = fs.GetFileType();
|
||||||
if (fileType != null)
|
if (fileType != null)
|
||||||
model.contentType = fileType.Mime;
|
model.contentType = fileType.Mime;
|
||||||
if (string.IsNullOrEmpty(model.contentType))
|
if (string.IsNullOrEmpty(model.contentType))
|
||||||
@ -112,76 +122,76 @@ namespace Teknik.Areas.API.V1.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Check content type restrictions (Only for encrypting server side
|
// Check content type restrictions (Only for encrypting server side
|
||||||
if (model.encrypt || !string.IsNullOrEmpty(model.key))
|
if (model.encrypt || !string.IsNullOrEmpty(model.key))
|
||||||
{
|
|
||||||
if (_config.UploadConfig.RestrictedContentTypes.Contains(model.contentType) || _config.UploadConfig.RestrictedExtensions.Contains(fileExt))
|
|
||||||
{
|
{
|
||||||
return Json(new { error = new { message = "File Type Not Allowed" } });
|
if (_config.UploadConfig.RestrictedContentTypes.Contains(model.contentType) || _config.UploadConfig.RestrictedExtensions.Contains(fileExt))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the key size and block size if empty
|
|
||||||
if (model.keySize <= 0)
|
|
||||||
model.keySize = _config.UploadConfig.KeySize;
|
|
||||||
if (model.blockSize <= 0)
|
|
||||||
model.blockSize = _config.UploadConfig.BlockSize;
|
|
||||||
|
|
||||||
// Save the file data
|
|
||||||
Upload.Models.Upload upload = UploadHelper.SaveFile(_dbContext, _config, model.file.OpenReadStream(), model.contentType, contentLength, model.encrypt, model.expirationUnit, model.expirationLength, fileExt, model.iv, model.key, model.keySize, model.blockSize);
|
|
||||||
|
|
||||||
if (upload != null)
|
|
||||||
{
|
|
||||||
string fileKey = upload.Key;
|
|
||||||
|
|
||||||
// Associate this with the user if they provided an auth key
|
|
||||||
if (User.Identity.IsAuthenticated)
|
|
||||||
{
|
|
||||||
User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
|
||||||
if (foundUser != null)
|
|
||||||
{
|
{
|
||||||
upload.UserId = foundUser.UserId;
|
return Json(new { error = new { message = "File Type Not Allowed" } });
|
||||||
_dbContext.Entry(upload).State = EntityState.Modified;
|
|
||||||
_dbContext.SaveChanges();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate delete key only if asked to
|
// Initialize the key size and block size if empty
|
||||||
if (!model.genDeletionKey)
|
if (model.keySize <= 0)
|
||||||
|
model.keySize = _config.UploadConfig.KeySize;
|
||||||
|
if (model.blockSize <= 0)
|
||||||
|
model.blockSize = _config.UploadConfig.BlockSize;
|
||||||
|
|
||||||
|
// Save the file data
|
||||||
|
Upload.Models.Upload upload = UploadHelper.SaveFile(_dbContext, _config, fs, model.contentType, contentLength, model.encrypt, model.expirationUnit, model.expirationLength, fileExt, model.iv, model.key, model.keySize, model.blockSize);
|
||||||
|
|
||||||
|
if (upload != null)
|
||||||
{
|
{
|
||||||
upload.DeleteKey = string.Empty;
|
string fileKey = upload.Key;
|
||||||
_dbContext.Entry(upload).State = EntityState.Modified;
|
|
||||||
_dbContext.SaveChanges();
|
// Associate this with the user if they provided an auth key
|
||||||
|
if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
User foundUser = UserHelper.GetUser(_dbContext, User.Identity.Name);
|
||||||
|
if (foundUser != null)
|
||||||
|
{
|
||||||
|
upload.UserId = foundUser.UserId;
|
||||||
|
_dbContext.Entry(upload).State = EntityState.Modified;
|
||||||
|
_dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate delete key only if asked to
|
||||||
|
if (!model.genDeletionKey)
|
||||||
|
{
|
||||||
|
upload.DeleteKey = string.Empty;
|
||||||
|
_dbContext.Entry(upload).State = EntityState.Modified;
|
||||||
|
_dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove the key if we don't want to save it
|
||||||
|
if (!model.saveKey)
|
||||||
|
{
|
||||||
|
upload.Key = null;
|
||||||
|
_dbContext.Entry(upload).State = EntityState.Modified;
|
||||||
|
_dbContext.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull all the information together
|
||||||
|
string fullUrl = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url });
|
||||||
|
var returnData = new
|
||||||
|
{
|
||||||
|
url = (model.saveKey || string.IsNullOrEmpty(fileKey)) ? fullUrl : fullUrl + "#" + fileKey,
|
||||||
|
fileName = upload.Url,
|
||||||
|
contentType = upload.ContentType,
|
||||||
|
contentLength = upload.ContentLength,
|
||||||
|
key = fileKey,
|
||||||
|
keySize = upload.KeySize,
|
||||||
|
iv = upload.IV,
|
||||||
|
blockSize = upload.BlockSize,
|
||||||
|
maxDownloads = upload.MaxDownloads,
|
||||||
|
expirationDate = upload.ExpireDate,
|
||||||
|
deletionKey = upload.DeleteKey
|
||||||
|
|
||||||
|
};
|
||||||
|
return Json(new { result = returnData });
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove the key if we don't want to save it
|
|
||||||
if (!model.saveKey)
|
|
||||||
{
|
|
||||||
upload.Key = null;
|
|
||||||
_dbContext.Entry(upload).State = EntityState.Modified;
|
|
||||||
_dbContext.SaveChanges();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull all the information together
|
|
||||||
string fullUrl = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url });
|
|
||||||
var returnData = new
|
|
||||||
{
|
|
||||||
url = (model.saveKey || string.IsNullOrEmpty(fileKey)) ? fullUrl : fullUrl + "#" + fileKey,
|
|
||||||
fileName = upload.Url,
|
|
||||||
contentType = upload.ContentType,
|
|
||||||
contentLength = upload.ContentLength,
|
|
||||||
key = fileKey,
|
|
||||||
keySize = upload.KeySize,
|
|
||||||
iv = upload.IV,
|
|
||||||
blockSize = upload.BlockSize,
|
|
||||||
maxDownloads = upload.MaxDownloads,
|
|
||||||
expirationDate = upload.ExpireDate,
|
|
||||||
deletionKey = upload.DeleteKey
|
|
||||||
|
|
||||||
};
|
|
||||||
return Json(new { result = returnData });
|
|
||||||
}
|
}
|
||||||
return Json(new { error = new { message = "Unable to save file" } });
|
return Json(new { error = new { message = "Unable to save file" } });
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Teknik.Logging;
|
using Teknik.Logging;
|
||||||
using Teknik.Areas.Users.Models;
|
using Teknik.Areas.Users.Models;
|
||||||
|
using Teknik.ContentScanningService;
|
||||||
|
|
||||||
namespace Teknik.Areas.Upload.Controllers
|
namespace Teknik.Areas.Upload.Controllers
|
||||||
{
|
{
|
||||||
@ -32,7 +33,6 @@ namespace Teknik.Areas.Upload.Controllers
|
|||||||
public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
|
public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[AllowAnonymous]
|
|
||||||
[TrackPageView]
|
[TrackPageView]
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
@ -77,7 +77,6 @@ namespace Teknik.Areas.Upload.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[AllowAnonymous]
|
|
||||||
[DisableRequestSizeLimit]
|
[DisableRequestSizeLimit]
|
||||||
public async Task<IActionResult> Upload([FromForm] UploadFileViewModel uploadFile)
|
public async Task<IActionResult> Upload([FromForm] UploadFileViewModel uploadFile)
|
||||||
{
|
{
|
||||||
@ -118,51 +117,58 @@ namespace Teknik.Areas.Upload.Controllers
|
|||||||
// convert file to bytes
|
// convert file to bytes
|
||||||
long contentLength = uploadFile.file.Length;
|
long contentLength = uploadFile.file.Length;
|
||||||
|
|
||||||
// Scan the file to detect a virus
|
|
||||||
if (_config.UploadConfig.VirusScanEnable)
|
|
||||||
{
|
|
||||||
using (Stream fs = uploadFile.file.OpenReadStream())
|
|
||||||
{
|
|
||||||
ClamClient clam = new ClamClient(_config.UploadConfig.ClamServer, _config.UploadConfig.ClamPort);
|
|
||||||
clam.MaxStreamSize = maxUploadSize;
|
|
||||||
ClamScanResult scanResult = await clam.SendAndScanFileAsync(fs);
|
|
||||||
|
|
||||||
switch (scanResult.Result)
|
|
||||||
{
|
|
||||||
case ClamScanResults.Clean:
|
|
||||||
break;
|
|
||||||
case ClamScanResults.VirusDetected:
|
|
||||||
return Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.InfectedFiles.First().VirusName, Url.SubRouteUrl("tos", "TOS.Index")) } });
|
|
||||||
case ClamScanResults.Error:
|
|
||||||
return Json(new { error = new { message = string.Format("Error scanning the file upload for viruses. {0}", scanResult.RawResult) } });
|
|
||||||
case ClamScanResults.Unknown:
|
|
||||||
return Json(new { error = new { message = string.Format("Unknown result while scanning the file upload for viruses. {0}", scanResult.RawResult) } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check content type restrictions (Only for encrypting server side
|
|
||||||
if (!uploadFile.options.Encrypt)
|
|
||||||
{
|
|
||||||
if (_config.UploadConfig.RestrictedContentTypes.Contains(uploadFile.fileType) || _config.UploadConfig.RestrictedExtensions.Contains(uploadFile.fileExt))
|
|
||||||
{
|
|
||||||
return Json(new { error = new { message = "File Type Not Allowed" } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (Stream fs = uploadFile.file.OpenReadStream())
|
using (Stream fs = uploadFile.file.OpenReadStream())
|
||||||
{
|
{
|
||||||
Models.Upload upload = UploadHelper.SaveFile(_dbContext,
|
ScanResult scanResult = null;
|
||||||
_config,
|
|
||||||
fs,
|
// Scan the file to detect a virus
|
||||||
uploadFile.fileType,
|
if (_config.UploadConfig.ClamConfig.Enabled)
|
||||||
contentLength,
|
{
|
||||||
!uploadFile.options.Encrypt,
|
var clamScanner = new ClamScanner(_config);
|
||||||
uploadFile.options.ExpirationUnit,
|
scanResult = await clamScanner.ScanFile(fs);
|
||||||
uploadFile.options.ExpirationLength,
|
}
|
||||||
uploadFile.fileExt,
|
|
||||||
uploadFile.iv, null,
|
// Scan the files against an endpoint based on hash
|
||||||
uploadFile.keySize,
|
if (_config.UploadConfig.HashScanConfig.Enabled && (scanResult == null || scanResult.ResultType == ScanResultType.Clean))
|
||||||
|
{
|
||||||
|
var hashScanner = new HashScanner(_config);
|
||||||
|
scanResult = await hashScanner.ScanFile(fs);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (scanResult?.ResultType)
|
||||||
|
{
|
||||||
|
case ScanResultType.Clean:
|
||||||
|
break;
|
||||||
|
case ScanResultType.VirusDetected:
|
||||||
|
return Json(new { error = new { message = string.Format("Virus Detected: {0}. As per our <a href=\"{1}\">Terms of Service</a>, Viruses are not permited.", scanResult.RawResult, Url.SubRouteUrl("tos", "TOS.Index")) } });
|
||||||
|
case ScanResultType.ChildPornography:
|
||||||
|
return Json(new { error = new { message = string.Format("Child Pornography Detected: As per our <a href=\"{0}\">Terms of Service</a>, Child Pornography is not permited.", Url.SubRouteUrl("tos", "TOS.Index")) } });
|
||||||
|
case ScanResultType.Error:
|
||||||
|
return Json(new { error = new { message = string.Format("Error scanning the file upload. {0}", scanResult.RawResult) } });
|
||||||
|
case ScanResultType.Unknown:
|
||||||
|
return Json(new { error = new { message = string.Format("Unknown result while scanning the file upload. {0}", scanResult.RawResult) } });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check content type restrictions (Only for encrypting server side
|
||||||
|
if (!uploadFile.options.Encrypt)
|
||||||
|
{
|
||||||
|
if (_config.UploadConfig.RestrictedContentTypes.Contains(uploadFile.fileType) || _config.UploadConfig.RestrictedExtensions.Contains(uploadFile.fileExt))
|
||||||
|
{
|
||||||
|
return Json(new { error = new { message = "File Type Not Allowed" } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Models.Upload upload = UploadHelper.SaveFile(_dbContext,
|
||||||
|
_config,
|
||||||
|
fs,
|
||||||
|
uploadFile.fileType,
|
||||||
|
contentLength,
|
||||||
|
!uploadFile.options.Encrypt,
|
||||||
|
uploadFile.options.ExpirationUnit,
|
||||||
|
uploadFile.options.ExpirationLength,
|
||||||
|
uploadFile.fileExt,
|
||||||
|
uploadFile.iv, null,
|
||||||
|
uploadFile.keySize,
|
||||||
uploadFile.blockSize);
|
uploadFile.blockSize);
|
||||||
if (upload != null)
|
if (upload != null)
|
||||||
{
|
{
|
||||||
@ -176,17 +182,20 @@ namespace Teknik.Areas.Upload.Controllers
|
|||||||
_dbContext.SaveChanges();
|
_dbContext.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Json(new { result = new
|
return Json(new
|
||||||
{
|
{
|
||||||
name = upload.Url,
|
result = new
|
||||||
url = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url }),
|
{
|
||||||
contentType = upload.ContentType,
|
name = upload.Url,
|
||||||
contentLength = StringHelper.GetBytesReadable(upload.ContentLength),
|
url = Url.SubRouteUrl("u", "Upload.Download", new { file = upload.Url }),
|
||||||
contentLengthRaw = upload.ContentLength,
|
contentType = upload.ContentType,
|
||||||
deleteUrl = Url.SubRouteUrl("u", "Upload.DeleteByKey", new { file = upload.Url, key = upload.DeleteKey }),
|
contentLength = StringHelper.GetBytesReadable(upload.ContentLength),
|
||||||
expirationUnit = uploadFile.options.ExpirationUnit.ToString(),
|
contentLengthRaw = upload.ContentLength,
|
||||||
expirationLength = uploadFile.options.ExpirationLength
|
deleteUrl = Url.SubRouteUrl("u", "Upload.DeleteByKey", new { file = upload.Url, key = upload.DeleteKey }),
|
||||||
} });
|
expirationUnit = uploadFile.options.ExpirationUnit.ToString(),
|
||||||
|
expirationLength = uploadFile.options.ExpirationLength
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Json(new { error = new { message = "Unable to upload file" } });
|
return Json(new { error = new { message = "Unable to upload file" } });
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Configuration\Configuration.csproj" />
|
<ProjectReference Include="..\Configuration\Configuration.csproj" />
|
||||||
|
<ProjectReference Include="..\ContentScanningService\ContentScanningService.csproj" />
|
||||||
<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" />
|
||||||
|
33
Utilities/Cryptography/SHA1.cs
Normal file
33
Utilities/Cryptography/SHA1.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Teknik.Utilities.Cryptography
|
||||||
|
{
|
||||||
|
public class SHA1
|
||||||
|
{
|
||||||
|
public static string Hash(string text)
|
||||||
|
{
|
||||||
|
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(text)))
|
||||||
|
{
|
||||||
|
return Hash(ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Hash(Stream stream)
|
||||||
|
{
|
||||||
|
var hash = default(string);
|
||||||
|
using (var algo = System.Security.Cryptography.SHA1.Create())
|
||||||
|
{
|
||||||
|
var hashBytes = algo.ComputeHash(stream);
|
||||||
|
|
||||||
|
// Return as hexadecimal string
|
||||||
|
hash = hashBytes.ToHex();
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,6 @@ namespace Teknik.Utilities.Cryptography
|
|||||||
public static string Hash(string value, string salt1, string salt2)
|
public static string Hash(string value, string salt1, string salt2)
|
||||||
{
|
{
|
||||||
SHA256Managed hash = new SHA256Managed();
|
SHA256Managed hash = new SHA256Managed();
|
||||||
SHA1 sha1 = new SHA1Managed();
|
|
||||||
// gen salt2 hash
|
// gen salt2 hash
|
||||||
byte[] dataSalt2 = Encoding.UTF8.GetBytes(salt2);
|
byte[] dataSalt2 = Encoding.UTF8.GetBytes(salt2);
|
||||||
byte[] salt2Bytes = hash.ComputeHash(dataSalt2);
|
byte[] salt2Bytes = hash.ComputeHash(dataSalt2);
|
||||||
@ -40,13 +39,7 @@ namespace Teknik.Utilities.Cryptography
|
|||||||
salt2Str += String.Format("{0:x2}", x);
|
salt2Str += String.Format("{0:x2}", x);
|
||||||
}
|
}
|
||||||
string dataStr = salt1 + value + salt2Str;
|
string dataStr = salt1 + value + salt2Str;
|
||||||
byte[] dataStrBytes = Encoding.UTF8.GetBytes(dataStr);
|
string sha1Str = SHA1.Hash(dataStr);
|
||||||
byte[] shaBytes = sha1.ComputeHash(dataStrBytes);
|
|
||||||
string sha1Str = string.Empty;
|
|
||||||
foreach (byte x in shaBytes)
|
|
||||||
{
|
|
||||||
sha1Str += String.Format("{0:x2}", x);
|
|
||||||
}
|
|
||||||
byte[] sha1Bytes = Encoding.UTF8.GetBytes(sha1Str);
|
byte[] sha1Bytes = Encoding.UTF8.GetBytes(sha1Str);
|
||||||
byte[] valueBytes = hash.ComputeHash(sha1Bytes);
|
byte[] valueBytes = hash.ComputeHash(sha1Bytes);
|
||||||
string hashString = string.Empty;
|
string hashString = string.Empty;
|
||||||
|
Loading…
Reference in New Issue
Block a user