webapi_application
- CSharp
//-------------------------------------------------------------------------
// The file structure for the CSharp project should include the following:
//-------------------------------------------------------------------------
// - Controller (folder)
// - FileUploadController.cs
// - HandshakeController.cs
// - Helpers (folder)
// - AesHelper.cs
// - Cache.cs
// - CacheItem.cs
// - IMteStateHelper.cs
// - MteStateHelper.cs
// - MteHelpers.cs
// - Models (folder)
// - Constants.cs
// - HandshakeModel.cs
// - MteStates.cs
// - MteResponse.cs
// - ResponseModel.cs
// - Repository (folder)
// - FileUploadRepository.cs
// - IFileUploadRepository.cs
// - mte.dll or libmte.so --> mte library
// - mteUploads (folder)
// - Program.cs
// - Startup.cs
// - Use NuGet Dependencies
// - Eclypses.MTE.Core(2.1.x)
// - PackageCSharpECDH(1.0.1)
//-------------------------------------------------------------------------
// Below only pertenent MTE file code is included
// Links are included at the bottom of this page to download entire project
//-------------------------------------------------------------------------
FileUploadController.cs
using Microsoft.AspNetCore.Mvc;
using MteDemoTest.Models;
using MteDemoTest.Repository;
using System;
using System.Threading.Tasks;
namespace MteDemoTest.Controllers
{
[ApiController]
public class FileUploadController : ControllerBase
{
private readonly IFileUploadRepository _fileUploadRepository;
public FileUploadController(IFileUploadRepository fileUploadRepository)
{
_fileUploadRepository = fileUploadRepository;
}
[HttpPost]
[Route("FileUpload/nomte")]
public async Task<ActionResult<ResponseModel<byte>>> FileUpload(string name)
{
try
{
ResponseModel<byte[]> result = await _fileUploadRepository.FileUpload(name, Request, false);
return new JsonResult(result);
}
catch (Exception ex)
{
ResponseModel<byte[]> exceptionResponse = new ResponseModel<byte[]>
{
Message = ex.Message,
ResultCode = Constants.RC_CONTROLLER_EXCEPTION,
Success = false
};
return new JsonResult(exceptionResponse);
}
}
[HttpPost]
[Route("FileUpload/mte")]
public async Task<ActionResult<byte[]>> FileUploadMte(string name)
{
try
{
ResponseModel<byte[]> result = await _fileUploadRepository.FileUpload(name, Request, true);
return new JsonResult(result);
}
catch (Exception ex)
{
ResponseModel<byte[]> exceptionResponse = new ResponseModel<byte[]>
{
Message = ex.Message,
ResultCode = Constants.RC_CONTROLLER_EXCEPTION,
Success = false
};
return new JsonResult(exceptionResponse);
}
}
}
}
MkeHelper.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Eclypses.MTE;
using MteDemoTest.Models;
namespace MteDemoTest.Helpers
{
public class MkeHelper
{
private readonly ILogger<MkeHelper> _logger;
// Mte Settings
private bool _encoderCreated = false;
private bool _decoderCreated = false;
public MkeHelper(ILogger<MkeHelper> logger)
{
_logger = logger;
}
#region EncodeMessage
/// <summary>
/// Encodes the message.
/// </summary>
/// <param name="encoder">The encoder.</param>
/// <param name="clearBytes">The clear bytes.</param>
/// <returns>MteEncoderResponse.</returns>
public MteEncoderResponse EncodeMessage(MteMkeEnc encoder, byte[]? clearBytes)
{
MteEncoderResponse response = new MteEncoderResponse { encoder = encoder };
try
{
//-----------------------------------------------
// If encoder not created, create it
//-----------------------------------------------
if (!_encoderCreated)
{
_logger.LogDebug("Create encoder.");
response.Status = response.encoder.StartEncrypt();
if (response.Status != MteStatus.mte_status_success)
{
_logger.LogError($"Error starting encoder: Status: {response.encoder.GetStatusName(response.Status)} / {response.encoder.GetStatusDescription(response.Status)}");
return response;
}
_encoderCreated = true;
}
//-----------------------------------------------
// encode bytes or finish up the encoder
//-----------------------------------------------
if (clearBytes == null)
{
//-----------------------------------------------
// If body is null then finish encoder and clear encoder
//-----------------------------------------------
var encodedBytes = response.encoder.FinishEncrypt(out MteStatus status);
if (status != MteStatus.mte_status_success)
{
response.Status = status;
_logger.LogError($"Error finishing encoder: Status: {response.encoder.GetStatusName(response.Status)} / {response.encoder.GetStatusDescription(response.Status)}");
return response;
}
response.Message = encodedBytes;
_encoderCreated = false;
}
else
{
//-----------------------------------------------
// Encode the body that is coming in
//-----------------------------------------------
response.Status = response.encoder.EncryptChunk(clearBytes);
if (response.Status != MteStatus.mte_status_success)
{
_logger.LogError($"Error encoder chunking: Status: {response.encoder.GetStatusName(response.Status)} / {response.encoder.GetStatusDescription(response.Status)}");
return response;
}
response.Message = clearBytes;
}
return response;
}
catch (Exception ex)
{
_logger.LogError($"Exception encoding: {ex.Message}");
Console.WriteLine(ex);
throw;
}
}
#endregion
#region DecodeMessage
/// <summary>
/// Decodes the message.
/// </summary>
/// <param name="decoder">The decoder.</param>
/// <param name="encodedBytes">The encoded bytes.</param>
/// <param name="bytesRead">The size of the bytes read.</param>
/// <returns>MteDecoderResponse.</returns>
public MteDecoderResponse DecodeMessage(MteMkeDec decoder, byte[]? encodedBytes, int bytesRead)
{
MteDecoderResponse response = new MteDecoderResponse { decoder = decoder };
try
{
//-----------------------------------------------
// If decoder not created, create it
//-----------------------------------------------
if (!_decoderCreated)
{
_logger.LogDebug("Create decoder.");
response.Status = response.decoder.StartDecrypt();
if (response.Status != MteStatus.mte_status_success)
{
_logger.LogError($"Error starting decoder: Status: {response.decoder.GetStatusName(response.Status)} / {response.decoder.GetStatusDescription(response.Status)}");
return response;
}
_decoderCreated = true;
}
//-----------------------------------------------
// encode bytes or finish up the encoder
//-----------------------------------------------
if (encodedBytes == null)
{
//-----------------------------------------------
// If encodedBytes is null then finish decoder and clear decoder
//-----------------------------------------------
var clearBytes = response.decoder.FinishDecrypt(out MteStatus status);
if (status != MteStatus.mte_status_success)
{
response.Status = status;
_logger.LogError($"Error finishing decoder: Status: {response.decoder.GetStatusName(response.Status)} / {response.decoder.GetStatusDescription(response.Status)}");
return response;
}
response.Message = clearBytes;
_decoderCreated = false;
}
else
{
//-----------------------------------------------
// Decode the body
//-----------------------------------------------
if(bytesRead == encodedBytes.Length)
{
response.Message = response.decoder.DecryptChunk(encodedBytes);
response.Status = (response.Message != null)
? MteStatus.mte_status_success
: MteStatus.mte_status_unsupported;
}
else
{
// Find out what the decoded length will be
var cipherBlocks = decoder.GetCiphersBlockBytes(decoder.GetCipher());
int buffBytes = bytesRead - cipherBlocks;
// allocate buffer for decoded data
response.Message = new byte[buffBytes];
int decryptError = response.decoder.DecryptChunk(encodedBytes, 0, bytesRead, response.Message, 0);
response.Status = (decryptError >= 0)
? MteStatus.mte_status_success
: MteStatus.mte_status_unsupported;
}
}
return response;
}
catch (Exception ex)
{
_logger.LogError($"Exception decoding: {ex.Message}");
Console.WriteLine(ex);
throw;
}
}
#endregion
}
}
IFileUploadRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using MteDemoTest.Models;
namespace MteDemoTest.Repository
{
public interface IFileUploadRepository
{
Task<ResponseModel<byte[]>> FileUpload(string fileName, HttpRequest request, bool useMte);
ResponseModel<HandshakeModel> StoreInitialClientHandshake(HandshakeModel model);
}
}
FileUploadRepsitory.cs
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Eclypses.MTE;
using MteDemoTest.Helpers;
using MteDemoTest.Models;
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;
namespace MteDemoTest.Repository
{
public class FileUploadRepository : IFileUploadRepository
{
//--------------------------
// File Upload Mte Settings
//--------------------------
private static string _uploadDirectory = "\\mteUploads";
private readonly ILogger<MkeHelper> _mteLogger;
private static int _bufferSize = 1024;
public FileUploadRepository(ILogger<MkeHelper> logger)
{
_mteLogger = logger;
}
#region FileUpload
/// <summary>
/// Files the upload.
/// </summary>
/// <param name="fileName">Name of the file.</param>
/// <param name="request">The request.</param>
/// <param name="useMte">if set to <c>true</c> [use mte].</param>
/// <returns>ResponseModel<System.Byte[]>.</returns>
public async Task<ResponseModel<byte[]>> FileUpload(string fileName, HttpRequest request, bool useMte)
{
ResponseModel<byte[]> response = new ResponseModel<byte[]>();
try
{
//--------------------------------------------------------
// Get the path to where we want to save the uploaded file
//--------------------------------------------------------
string subPath = Directory.GetCurrentDirectory() + _uploadDirectory;
//------------------------------------------
// Check if directory exists, if not create
//------------------------------------------
bool exists = Directory.Exists(subPath);
if (!exists)
Directory.CreateDirectory(subPath);
//------------------
// Create file name
//------------------
string file = Path.Combine(subPath, fileName);
//------------------------------------------------
// If file exists already use different file name
//------------------------------------------------
if (File.Exists(file))
{
Random rnd = new Random();
while (File.Exists(file))
{
file = Path.Combine(subPath, $"N{rnd.Next(999)}{fileName}");
}
}
//--------------------------------------------------------
// Run different methods depending on if using MTE or not
//--------------------------------------------------------
if (useMte)
{
response = await UploadFileMte(file, request);
}
else
{
response = await UploadFileNoMte(file, request);
}
}
catch (Exception ex)
{
response.Message = $"Exception uploading file with MTE: {ex.Message}";
response.Success = false;
response.ResultCode = Constants.RC_REPOSITORY_EXCEPTION;
_mteLogger.LogError(ex.Message);
}
return response;
}
#endregion
#region StoreInitialClientHandshake
/// <summary>
/// Stores the initial client handshake.
/// Creates the MTE encode and decode states
/// </summary>
/// <param name="model">The model.</param>
/// <returns>ResponseModel<HandshakeModel>.</returns>
public ResponseModel<HandshakeModel> StoreInitialClientHandshake(HandshakeModel model)
{
ResponseModel<HandshakeModel> response = new ResponseModel<HandshakeModel>
{
Data = new HandshakeModel { ConversationIdentifier = model.ConversationIdentifier }
};
try
{
//----------------------
// Create DH containers
//----------------------
EclypsesECDH encoderEcdh = new EclypsesECDH();
EclypsesECDH decoderEcdh = new EclypsesECDH();
//-----------------------------------------------------
// create encoder shared secret from decoder public key
//-----------------------------------------------------
var encoderSharedSecret = encoderEcdh.ProcessPartnerPublicKey(model.ClientDecoderPublicKey);
//-----------------------------------------------------
// create decoder shared secret from encoder public key
//-----------------------------------------------------
var decoderSharedSecret = decoderEcdh.ProcessPartnerPublicKey(model.ClientEncoderPublicKey);
//---------------------------------------------------------
// Create a timestamp that both ends can use for the Nonce
//---------------------------------------------------------
response.Data.Timestamp = DateTime.Now.ToString("yyMMddHHmmssffff");
response.Data.ClientDecoderPublicKey = decoderSharedSecret.PublicKey;
response.Data.ClientEncoderPublicKey = encoderSharedSecret.PublicKey;
//---------------------------------------------------------
// Create and store MTE Encoder and Decoder for this Client
//---------------------------------------------------------
ResponseModel mteResponse = _stateHelper.CreateMteStates(model.ConversationIdentifier, encoderSharedSecret.SharedSecret, decoderSharedSecret.SharedSecret, Convert.ToUInt64(response.Data.Timestamp));
response.Message = mteResponse.Message;
response.ResultCode = mteResponse.ResultCode;
response.Success = mteResponse.Success;
//-------------------------
// Clear current ecdh
//-------------------------
encoderEcdh.ClearContainer();
decoderEcdh.ClearContainer();
}
catch (Exception ex)
{
response.Message = $"Exception initial client handshake. Ex: {ex.Message}";
response.ResultCode = Constants.RC_REPOSITORY_EXCEPTION;
response.Success = false;
}
return response;
}
#endregion
#region EncodeResponse
/// <summary>
/// Encodes the response.
/// </summary>
/// <param name="outgoingResponse">The outgoing response.</param>
/// <param name="clientId">The Id of the client.</param>
/// <returns>ResponseModel<System.Byte[]>.</returns>
private ResponseModel<byte[]> EncodeResponse(string outgoingResponse, string clientId)
{
ResponseModel<byte[]> response = new ResponseModel<byte[]>();
try
{
//-----------------------------------------
// Get encryption IV and create AES Helper
//-----------------------------------------
var enc = new AesHelper();
string myIV = Constants.MteClientState.Get(Constants.IVKey);
MteMkeEnc encoder = new MteMkeEnc();
MkeHelper mteHelper = new MkeHelper(_mteLogger);
//-------------------
// Get encoder state
//-------------------
string encoderState = Constants.MteClientState.Get($"{Constants.EncoderPrefix}{clientId}");
if (string.IsNullOrWhiteSpace(encoderState))
{
response.Message = "MTE state not found, please handshake again.";
response.ResultCode = Constants.RC_MTE_STATE_NOT_FOUND;
response.Success = false;
}
//----------------------
// Decrypt encoder state
//----------------------
var decryptedState = enc.Decrypt(encoderState, clientId, myIV);
//-----------------------------------------
// Restore MTE Encoder and check for error
//-----------------------------------------
MteStatus encoderStatus = encoder.RestoreStateB64(decryptedState);
if (encoderStatus != MteStatus.mte_status_success)
{
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.Message = $"Error restoring state of encoder: Status: {encoder.GetStatusName(encoderStatus)} / {encoder.GetStatusDescription(encoderStatus)}";
response.Success = false;
return response;
}
//--------------------------
// Encode chunk the message
//--------------------------
MteEncoderResponse result = mteHelper.EncodeMessage(encoder, Encoding.UTF8.GetBytes(outgoingResponse));
if (result.Status != MteStatus.mte_status_success)
{
response.Success = false;
response.ResultCode = Constants.RC_MTE_ENCODE_CHUNK_ERROR;
response.Message = "Failed to encode. Status: "
+ encoder.GetStatusName(result.Status) + " / "
+ encoder.GetStatusDescription(result.Status);
return response;
}
//--------------------
// Finish the encoder
//--------------------
MteEncoderResponse finalResult = mteHelper.EncodeMessage(result.encoder, null);
if (finalResult.Status != MteStatus.mte_status_success)
{
response.Success = false;
response.ResultCode = Constants.RC_MTE_ENCODE_FINISH_ERROR;
response.Message = "Failed to finish encode. Status: "
+ encoder.GetStatusName(finalResult.Status) + " / "
+ encoder.GetStatusDescription(finalResult.Status);
return response;
}
//--------------------------------------------------------------
// Save encoder state, encrypt state, and store to memory cache
//--------------------------------------------------------------
encoderState = finalResult.encoder.SaveStateB64();
var encryptedState = enc.Encrypt(encoderState, clientId, myIV);
Constants.MteClientState.Store($"{Constants.EncoderPrefix}{clientId}", encryptedState, TimeSpan.FromMinutes(Constants.ExpireMinutes));
//------------------------------------------------
// Check to see if the final result had more text
// If so append it to the result
//------------------------------------------------
finalResult.Message ??= new byte[0];
response.Data = new byte[result.Message.Length + finalResult.Message.Length];
Buffer.BlockCopy(result.Message, 0, response.Data, 0, result.Message.Length);
Buffer.BlockCopy(finalResult.Message, 0, response.Data, result.Message.Length, finalResult.Message.Length);
}
catch (Exception ex)
{
response.Success = false;
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.Message = $"Exception Encoding response: {ex.Message}";
}
return response;
}
#endregion
#region UploadFileMte
/// <summary>
/// Uploads the file mte.
/// </summary>
/// <param name="file">The file.</param>
/// <param name="request">The request.</param>
/// <returns>ResponseModel<System.Byte[]>.</returns>
private async Task<ResponseModel<byte[]>> UploadFileMte(string file, HttpRequest request)
{
ResponseModel<byte[]> response = new ResponseModel<byte[]>();
try
{
//-----------------------------------------
// Get encryption IV and create AES Helper
//-----------------------------------------
var enc = new AesHelper();
string myIV = Constants.MteClientState.Get(Constants.IVKey);
//---------------------------------
// Get clientId from request header
//---------------------------------
string clientId = request.Headers[Constants.ClientIdHeader];
// add check to make sure we get an entry
if (string.IsNullOrWhiteSpace(clientId))
{
response.Message = $"ClientId is empty or null, must have identifier in Header.";
response.ResultCode = Constants.RC_VALIDATION_ERROR;
response.Success = false;
return response;
}
//-------------------
// Create MTE Helper
//-------------------
MkeHelper mteHelper = new MkeHelper(_mteLogger);
//-------------------------------
// Get decoder state and decrypt
//-------------------------------
string decoderState = Constants.MteClientState.Get($"{Constants.DecoderPrefix}{clientId}");
if (string.IsNullOrWhiteSpace(decoderState))
{
response.Message = "MTE state not found, please handshake again.";
response.ResultCode = Constants.RC_MTE_STATE_NOT_FOUND;
response.Success = false;
}
var decryptedState = enc.Decrypt(decoderState, clientId, myIV);
//--------------------------------------
// Restore decoder and check for errors
//--------------------------------------
MteMkeDec decoder = new MteMkeDec();
MteStatus decoderStatus = decoder.RestoreStateB64(decryptedState);
if (decoderStatus != MteStatus.mte_status_success)
{
response.ResultCode = Constants.RC_MTE_DECODE_EXCEPTION;
response.Message = $"Error restoring state of decoder: Status: {decoder.GetStatusName(decoderStatus)} / {decoder.GetStatusDescription(decoderStatus)}";
response.Success = false;
return response;
}
//------------------------------------------------
// iterate through request body and write to file
//------------------------------------------------
await using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write,
FileShare.None, _bufferSize, useAsync: true))
{
var buffer = new byte[_bufferSize];
var bytesRead = default(int);
while ((bytesRead = await request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
//-----------------
// decode the data
//-----------------
MteDecoderResponse decResponse = mteHelper.DecodeMessage(decoder, buffer, bytesRead);
if (decResponse.Status != MteStatus.mte_status_success)
{
response.Data = null;
response.Success = false;
response.ResultCode = Constants.RC_MTE_DECODE_CHUNK_ERROR;
response.Message = $"Failed to Decode chunk. Status: " +
$"{decoder.GetStatusName(decResponse.Status)} / " +
$"{decoder.GetStatusDescription(decResponse.Status)}";
return response;
}
//----------------------------------
// write decoded data to file
// debuging stuff --> look at bytes
//----------------------------------
string thesebytes = Encoding.Default.GetString(decResponse.Message);
await fs.WriteAsync(decResponse.Message, 0, decResponse.Message.Length);
//---------------------------------------------------
// set the decoder to the latest version of decoder
//---------------------------------------------------
decoder = decResponse.decoder;
}
//--------------------------------------
// Finish the decoding chunking session
//--------------------------------------
MteDecoderResponse decFinalResponse = mteHelper.DecodeMessage(decoder, null, 0);
if (decFinalResponse.Status != MteStatus.mte_status_success)
{
response.Data = null;
response.Success = false;
response.ResultCode = Constants.RC_MTE_DECODE_FINISH_ERROR;
response.Message = "Failed to finish decode chunk. Status: "
+ decoder.GetStatusName(decFinalResponse.Status) + " / "
+ decoder.GetStatusDescription(decFinalResponse.Status);
return response;
}
//-------------------------------
// Encrypt and save decoder state
//-------------------------------
decoderState = decFinalResponse.decoder.SaveStateB64();
var encryptedState = enc.Encrypt(decoderState, clientId, myIV);
Constants.MteClientState.Store($"{Constants.DecoderPrefix}{clientId}", encryptedState, TimeSpan.FromMinutes(Constants.ExpireMinutes));
//-----------------------------------------------------------------------
// Check if there is additional bytes if not initialize empty byte array
//-----------------------------------------------------------------------
if (decFinalResponse.Message.Length <= 0) { decFinalResponse.Message = new byte[0]; }
//------------------------------------
// Append the final data to the file
//------------------------------------
string finishbytes = Encoding.Default.GetString(decFinalResponse.Message);
await fs.WriteAsync(decFinalResponse.Message, 0, decFinalResponse.Message.Length);
}
response.Message = null;
return EncodeResponse("Successfully uploaded file.", clientId);
}
catch (Exception ex)
{
response.Message = $"Exception uploading file with MTE: {ex.Message}";
response.Success = false;
response.ResultCode = Constants.RC_MTE_DECODE_EXCEPTION;
_mteLogger.LogError(ex.Message);
return response;
}
}
#endregion
#region UploadFileNoMte
/// <summary>
/// Uploads the file no mte.
/// </summary>
/// <param name="file">The file.</param>
/// <param name="request">The request.</param>
/// <returns>ResponseModel<System.Byte[]>.</returns>
private async Task<ResponseModel<byte[]>> UploadFileNoMte(string file, HttpRequest request)
{
ResponseModel<byte[]> response = new ResponseModel<byte[]>();
try
{
//------------------------------------------------
// iterate through request body and write to file
//------------------------------------------------
await using (FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write,
FileShare.None, _bufferSize, useAsync: true))
{
var buffer = new byte[_bufferSize];
var bytesRead = default(int);
while ((bytesRead = await request.Body.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
//--------------------
// write data to file
//--------------------
await fs.WriteAsync(buffer, 0, bytesRead);
}
}
response.Data = Encoding.UTF8.GetBytes("Successfully uploaded file.");
}
catch (Exception ex)
{
response.Message = $"Exception uploading file with MTE: {ex.Message}";
response.Success = false;
response.ResultCode = Constants.RC_REPOSITORY_EXCEPTION;
_mteLogger.LogError(ex.Message);
}
return response;
}
#endregion
}
}