Skip to main content

MTE File Upload Code Samples

Purpose

Samples expand on our "Examples" by providing longer snippets of code demonstrating the entire processes. The objective of an MTE File Upload "Samples" is to provide full samples of code of how to use the MTE Chunking for file uploads.

This sample uses a simple console application for the user side of the application where the user selects a file, then the contents are encoded after which it is sent to a web API. The web API controller grabs the file, decodes the contents and then saves it to the drive in an "mteUploads" folder.

IMPORTANT

This is ONLY for demonstration purposes and should not be used in a production environment as is.

Console Application

Here is the console application code. There are two classes, the program class that contains the main program and the UploadFile class where the file is encoded and then uploaded to the API.

//-------------------------------------------------------------------------
// The file structure for the CSharp project should include the following:
//-------------------------------------------------------------------------
// - Models (folder)
// - Constants.cs
// - HandshakeModel.cs
// - HandshakeResponse.cs
// - ResponseModel.cs
// - UploadResponse.cs
// - Add NuGet dependencies
// - Eclypses.MTE.Core(2.1.x)
// - PackageCSharpECDH(1.0.1)
// - mte.dll or libmte.so --> mte library
// - Program.cs
// - UploadFiles.cs
//-------------------------------------------------------------------------
// Below only pertinent MTE file code is included
// Links are included at the bottom of this page to download entire project
//-------------------------------------------------------------------------
Program.cs
using MteConsoleUploadTest.Models;
using System;
using System.Windows.Forms;

namespace MteConsoleUploadTest
{
class Program
{
//------------------
// set if using MTE
//------------------
private static bool useMte = true;


[STAThread]
static void Main()
{
//--------------------------
// Create Upload File class
//--------------------------
UploadFile uploadFile = new UploadFile();

//-------------------------------------------------
// Initialize encoder and decoder and set clientId
//-------------------------------------------------
HandshakeResponse handshake = new HandshakeResponse { EncoderState = String.Empty, DecoderState = String.Empty };
string clientId = Guid.NewGuid().ToString();

//---------------------------------------------------------
// Handshake with server and create MTE if "useMte == true"
//---------------------------------------------------------
if (useMte)
{
ResponseModel<HandshakeResponse> handshakeResponse = uploadFile.HandshakeWithServer(clientId);
if (!handshakeResponse.Success)
{
throw new ApplicationException($"Error trying to handshake with server: {handshakeResponse.Message}");
}

//-------------------------------
// Set decoder and encoder state
//-------------------------------
handshake.DecoderState = handshakeResponse.Data.DecoderState;
handshake.EncoderState = handshakeResponse.Data.EncoderState;
}

while (true)
{
//--------------------------------
// Prompt user for file to upload
//--------------------------------
string path = string.Empty;
while (string.IsNullOrWhiteSpace(path))
{
OpenFileDialog dialog = new OpenFileDialog();
if (DialogResult.OK == dialog.ShowDialog())
{
path = dialog.FileName;
}
}

//---------------------
// Send file to server
//---------------------
ResponseModel<UploadResponse> uploadResponse = uploadFile.Send(path, useMte, handshake.EncoderState, handshake.DecoderState, clientId);

if (!uploadResponse.Success)
{
throw new ApplicationException($"Error uploading file: {uploadResponse.Message}");
}
Console.WriteLine(uploadResponse.Data.ServerResponse);

//--------------------------------------------------------
// Update Encoder and Decoder states to be latest version
//--------------------------------------------------------
handshake.EncoderState = uploadResponse.Data.EncoderState;
handshake.DecoderState = uploadResponse.Data.DecoderState;

//--------------------------------------
// Promot to upload another file or not
//--------------------------------------
Console.WriteLine($"Would you like to upload an additional file? (y/n)");
string sendAdditional = Console.ReadLine();
if (sendAdditional != null && sendAdditional.Equals("n", StringComparison.InvariantCultureIgnoreCase))
{
break;
}
}
}
}
}
UploadFile.cs
using Eclypses.MTE;
using MteConsoleUploadTest.Models;
using PackageCSharpECDH;
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;

namespace MteConsoleUploadTest
{
public class UploadFile
{
#region UploadFile Constructor
//------------------
// Set Rest API URL
//------------------
private static readonly string _restAPIName = "http://localhost:52603";

//---------------------------
// Set upload File constants
//---------------------------
private const int _maxChunkSize = 1024;
private HttpWebRequest _webRequest = null;
private FileStream _fileReader = null;
private Stream _requestStream = null;
private static JsonSerializerOptions _jsonOptions;
private static readonly string _jsonContentType = "application/json";


/// <summary>Initializes a new instance of the <see cref="T:MteConsoleUploadTest.UploadFile" /> class.</summary>
public UploadFile()
{
//-------------------------------------
// Set up Json to be not case sensative
//-------------------------------------
_jsonOptions = new JsonSerializerOptions();
_jsonOptions.PropertyNameCaseInsensitive = true;
_jsonOptions.Converters.Add(new JsonStringEnumConverter());
}
#endregion

#region Send
/// <summary>
/// Sends the file up to the server
/// </summary>
/// <param name="path">The file path.</param>
/// <param name="useMte">Whether or not to use MTE when sending file.</param>
/// <param name="encoderState">The current encoder state.</param>
/// <param name="decoderState">The current decoder state.</param>
/// <param name="clientid">The client id.</param>
/// <returns>System.Int32.</returns>
public ResponseModel<UploadResponse> Send(string path, bool useMte, string encoderState, string decoderState, string clientid)
{
ResponseModel<UploadResponse> uploadResponse = new()
{
Data = new UploadResponse
{
EncoderState = encoderState,
DecoderState = decoderState
}
};
try
{
//------------------------
// Create default encoder
//------------------------
MteMkeEnc encoder = new MteMkeEnc();

//------------------------------
// Get file info and create url
//------------------------------
FileInfo file = new FileInfo(path);
string urlType = (useMte) ? "mte" : "nomte";
string fileUrl = Path.Combine($"{_restAPIName}/FileUpload/", urlType + "?name=" + file.Name);

//-----------------------------------
// Create file stream and webRequest
//-----------------------------------
_fileReader = new FileStream(path, FileMode.Open, FileAccess.Read);
_webRequest = (HttpWebRequest)WebRequest.Create(fileUrl);
_webRequest.Method = "POST";
if (useMte)
{
//--------------------------------------------------
// If we are using the MTE adjust the content length
//--------------------------------------------------
int additionalBytes = encoder.EncryptFinishBytes();
long finalLength = (long)(_fileReader.Length + additionalBytes);
_webRequest.ContentLength = finalLength;
}
else
{
//-------------------------------------------------------------
// Regular request will have the file length as content length
//-------------------------------------------------------------
_webRequest.ContentLength = _fileReader.Length;
}
_webRequest.Timeout = 600000;

//-------------------------
// Add client id to header if not null
//-------------------------
if (!string.IsNullOrWhiteSpace(clientid))
{
_webRequest.Headers.Add(Constants.ClientIdHeader, clientid);
}

_webRequest.Credentials = CredentialCache.DefaultCredentials;
_webRequest.AllowWriteStreamBuffering = false;
_requestStream = _webRequest.GetRequestStream();

long fileSize = _fileReader.Length;
long remainingBytes = fileSize;
int numberOfBytesRead = 0, done = 0;


if (useMte)
{
//----------------------------
// Restore encoder from state
//----------------------------
MteStatus status = encoder.RestoreStateB64(encoderState);
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
uploadResponse.Message = $"Failed to restore MTE encoder engine. Status: {encoder.GetStatusName(status)} / {encoder.GetStatusDescription(status)}";
return uploadResponse;
}

//----------------------------
// start the chunking session
//----------------------------
status = encoder.StartEncrypt();
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to start encode chunk. Status: "
+ encoder.GetStatusName(status) + " / "
+ encoder.GetStatusDescription(status);
return uploadResponse;
}
}

//------------------------
// Break up files to send
//------------------------
while (numberOfBytesRead < fileSize)
{
byte[] fileData;
SetByteArray(out fileData, remainingBytes);
done = _fileReader.Read(fileData, 0, fileData.Length);
if (useMte)
{
//------------------------------------------------------------
// Encode the data in place - encoded data put back in buffer
//------------------------------------------------------------
MteStatus chunkStatus = encoder.EncryptChunk(fileData, 0, fileData.Length);
if (chunkStatus != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to encode chunk. Status: "
+ encoder.GetStatusName(chunkStatus) + " / "
+ encoder.GetStatusDescription(chunkStatus);
return uploadResponse;
}
}
//-----------------------------
// Write the data to the stream
//-----------------------------
_requestStream.Write(fileData, 0, fileData.Length);
numberOfBytesRead += done;
remainingBytes -= done;
}

if (useMte)
{
//----------------------------
// Finish the chunking session
//----------------------------
byte[] finalEncodedChunk = encoder.FinishEncrypt(out MteStatus finishStatus);
if (finishStatus != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
uploadResponse.Message = "Failed to finish encode chunk. Status: "
+ encoder.GetStatusName(finishStatus) + " / "
+ encoder.GetStatusDescription(finishStatus);
return uploadResponse;
}

//------------------------------------
// Append the final data to the stream
//------------------------------------
_requestStream.Write(finalEncodedChunk, 0, finalEncodedChunk.Length);

//-----------------------
// Save the encoderState
//-----------------------
uploadResponse.Data.EncoderState = encoder.SaveStateB64();

}

//------------------
// Get the response.
//------------------
WebResponse response = _webRequest.GetResponse();

//---------------------
// get the return text
//---------------------
using Stream data = response.GetResponseStream();
using var reader = new StreamReader(data);
string text = reader.ReadToEnd();

ResponseModel<byte[]> textResponse =
JsonSerializer.Deserialize<ResponseModel<byte[]>>(text, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });

if (!textResponse.Success)
{
//-----------------------------------
// Check if we need to "re-handshake"
//-----------------------------------
if (textResponse.ResultCode.Equals(Constants.RC_MTE_STATE_NOT_FOUND,
StringComparison.InvariantCultureIgnoreCase))
{
//-------------------------------------------------------------------------
// the server does not have this client's state - we should "re-handshake"
//-------------------------------------------------------------------------
var handshakeRessponse = HandshakeWithServer(clientid);

//-----------------------------------------------------------
// return response, if successful give message to try again.
//-----------------------------------------------------------
uploadResponse.Success = handshakeRessponse.Success;
uploadResponse.Message = (uploadResponse.Success)
? "Server lost MTE state, client needed to handshake again, handshake successful, please try again."
: handshakeRessponse.Message;
uploadResponse.ResultCode = handshakeRessponse.ResultCode;
return uploadResponse;
}
}

if (useMte)
{
//------------------
// Restroe decoder
//------------------
MteMkeDec decoder = new MteMkeDec();
MteStatus status = decoder.RestoreStateB64(decoderState);
if (status != MteStatus.mte_status_success)
{
uploadResponse.Success = false;
uploadResponse.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
uploadResponse.Message = $"Failed to restore the MTE decoder engine. Status: {decoder.GetStatusName(status)} / {decoder.GetStatusDescription(status)}";
return uploadResponse;
}

//----------------------------
// start the chunking session
//----------------------------
status = decoder.StartDecrypt();
if (status != MteStatus.mte_status_success)
{
throw new Exception("Failed to start decode chunk. Status: "
+ decoder.GetStatusName(status) + " / "
+ decoder.GetStatusDescription(status));
}
//-----------------
// Decode the data
//-----------------
byte[] decodedChunk = decoder.DecryptChunk(textResponse.Data);
var clearBytes = decoder.FinishDecrypt(out MteStatus finalStatus);
if (clearBytes == null) { clearBytes = new byte[0]; }
if (finalStatus != MteStatus.mte_status_success)
{
throw new Exception("Failed to finish decode. Status: "
+ decoder.GetStatusName(finalStatus) + " / "
+ decoder.GetStatusDescription(finalStatus));
}
//---------------------
// Set decoded message
//---------------------
byte[] decodedMessage = new byte[decodedChunk.Length + clearBytes.Length];
Buffer.BlockCopy(decodedChunk, 0, decodedMessage, 0, decodedChunk.Length);
Buffer.BlockCopy(clearBytes, 0, decodedMessage, decodedChunk.Length, clearBytes.Length);

//----------------------------
// Return the server response
//----------------------------
uploadResponse.Data.ServerResponse = Encoding.UTF8.GetString(decodedMessage);

//------------------------
// Save the decoder state
//------------------------
uploadResponse.Data.DecoderState = decoder.SaveStateB64();
}
else
{
//----------------------------
// Return the server response
//----------------------------
uploadResponse.Data.ServerResponse = Encoding.UTF8.GetString(textResponse.Data);
}

}
catch (Exception e)
{
uploadResponse.Message = $"Exception uploading file to server. Ex: {e.Message}";
uploadResponse.ResultCode = Constants.RC_UPLOAD_EXCEPTION;
uploadResponse.Success = false;
}
return uploadResponse;
}
#endregion

#region SetByteArray
/// <summary>
/// Sets the byte array.
/// </summary>
/// <param name="fileData">The file data.</param>
/// <param name="bytesLeft">The bytes left.</param>
private void SetByteArray(out byte[] fileData, long bytesLeft)
{
fileData = bytesLeft < _maxChunkSize ? new byte[bytesLeft] : new byte[_maxChunkSize];
}
#endregion

#region HandshakeWithServer
/// <summary>Handshakes the with server.</summary>
/// <param name="clientId">The client identifier.</param>
/// <returns>ResponseModel&lt;HandshakeResponse&gt;.</returns>
public ResponseModel<HandshakeResponse> HandshakeWithServer(string clientId)
{
ResponseModel<HandshakeResponse> response = new ResponseModel<HandshakeResponse> { Data = new HandshakeResponse() };
try
{
//--------------------------------
// create clientId for this client
//--------------------------------
HandshakeModel handshake = new HandshakeModel { ConversationIdentifier = clientId };

//-------------------------------------------
// Create eclypses DH containers for handshake
//-------------------------------------------
EclypsesECDH encoderEcdh = new EclypsesECDH();
EclypsesECDH decoderEcdh = new EclypsesECDH();

//-------------------------------------------
// Get the public key to send to other side
//-------------------------------------------
handshake.ClientEncoderPublicKey = encoderEcdh.GetPublicKey(encoderEcdh.GetTheContainer());
handshake.ClientDecoderPublicKey = decoderEcdh.GetPublicKey(decoderEcdh.GetTheContainer());

//-------------------
// Perform handshake
//-------------------
string handshakeResponse =
MakeHttpCall($"{_restAPIName}/api/handshake", HttpMethod.Post, handshake.ConversationIdentifier,
_jsonContentType, JsonSerializer.Serialize(handshake, _jsonOptions)).Result;

//---------------------------------------
// Deserialize the result from handshake
//---------------------------------------
ResponseModel<HandshakeModel> serverResponse =
JsonSerializer.Deserialize<ResponseModel<HandshakeModel>>(handshakeResponse, _jsonOptions);

//---------------------------------------
// If handshake was not successful break
//---------------------------------------
if (!serverResponse.Success)
{
response.Success = serverResponse.Success;
response.Message = serverResponse.Message;
response.ResultCode = serverResponse.ResultCode;
Console.WriteLine($"Error making DH handshake for Client {clientId}: {serverResponse.Message}");
return response;
}

//----------------------
// Create shared secret
//----------------------
var encoderSharedSecretModel = encoderEcdh.ProcessPartnerPublicKey(serverResponse.Data.ClientEncoderPublicKey);
var decoderSharedSecretModel = decoderEcdh.ProcessPartnerPublicKey(serverResponse.Data.ClientDecoderPublicKey);

//--------------------------------
// Set MTE settings and get state
//--------------------------------
if (!ulong.TryParse(serverResponse.Data.Timestamp, out ulong nonce))
{
response.Success = false;
response.Message = $"Nonce is not valid ulong: {serverResponse.Data.Timestamp}.";
response.ResultCode = Constants.RC_INVALID_NONCE;
return response;
}
//----------------------------
// Set encoder and save state
//----------------------------
MteMkeEnc encoder = new MteMkeEnc();
encoder.SetEntropy(encoderSharedSecretModel.SharedSecret);
encoder.SetNonce(nonce);
MteStatus status = encoder.Instantiate(handshake.ConversationIdentifier);
if (status != MteStatus.mte_status_success)
{
response.Success = false;
response.Message = $"Failed to initialize the MTE encoder engine. Status: {encoder.GetStatusName(status)} / {encoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_STATE_CREATION;
return response;
}
response.Data.EncoderState = encoder.SaveStateB64();

//----------------------------
// Set decoder and save state
//----------------------------
MteMkeDec decoder = new MteMkeDec();
decoder.SetEntropy(decoderSharedSecretModel.SharedSecret);
decoder.SetNonce(nonce);
status = decoder.Instantiate(handshake.ConversationIdentifier);
if (status != MteStatus.mte_status_success)
{
response.Success = false;
response.Message = $"Failed to initialize the MTE decoder engine. Status: {decoder.GetStatusName(status)} / {decoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_STATE_CREATION;
return response;
}
response.Data.DecoderState = decoder.SaveStateB64();
}
catch (Exception ex)
{
response.Message = $"Exception handshaking with server. Ex: {ex.Message}";
response.ResultCode = Constants.RC_HANDSHAKE_EXCEPTION;
response.Success = false;
}
return response;
}
#endregion

#region MakeHttpCall (Async)
/// <summary>
/// Makes the HTTP call.
/// </summary>
/// <param name="url">The URL.</param>
/// <param name="method">The method.</param>
/// <param name="clientId">The client identifier.</param>
/// <param name="contentType">Type of the content.</param>
/// <param name="payload">The payload.</param>
/// <returns>System.String.</returns>
private async Task<string> MakeHttpCall(string url, HttpMethod method, string clientId, string contentType, string payload = null)
{
//----------------------------------------------
// Declare return payload string and initialize
//----------------------------------------------
string returnPayload = string.Empty;
try
{
//-----------------------------------------
// Set URI and other default Http settings
//-----------------------------------------
Uri uri = new Uri($"{url}");
HttpResponseMessage responseMessage = null;
var handler = new HttpClientHandler() { };
using var client = new HttpClient(handler) { BaseAddress = uri };

//-------------------------
// Add client id to header
//-------------------------
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Add(Constants.ClientIdHeader, clientId);

//---------------------------------------------------
// Check if we have a payload or not and send request
//---------------------------------------------------
if (string.IsNullOrWhiteSpace(payload))
{
//------------------------------------------------------------------------
// The only two methods that will not have any content are delete and get
//------------------------------------------------------------------------
responseMessage = method switch
{
HttpMethod m when m == HttpMethod.Delete => await client.DeleteAsync(uri),
HttpMethod m when m == HttpMethod.Get => await client.GetAsync(uri),
_ => await client.GetAsync(uri)
};
}
else
{
//Console.WriteLine($"Sending: '{payload}'");

//-----------------------------------------------
// Set byte payload and content type for request
//-----------------------------------------------
byte[] bytePayload = Encoding.ASCII.GetBytes(payload);
ByteArrayContent byteContent = new ByteArrayContent(bytePayload, 0, bytePayload.Length);
byteContent.Headers.Add("Content-Type", contentType);

//---------------------------------------------
// Create httpRequest with given byte payload
//---------------------------------------------
var httpRequest = new HttpRequestMessage
{
Method = method,
RequestUri = uri,
Content = byteContent
};

//------------------------
// Send out client request
//------------------------
responseMessage = await client.SendAsync(httpRequest).ConfigureAwait(false);
}

//------------------------------------------------------
// If the response is successful get the return payload
//------------------------------------------------------
if (responseMessage.IsSuccessStatusCode)
{
//------------------------------------------
// Use read as string if other content type
//------------------------------------------
returnPayload = await responseMessage.Content.ReadAsStringAsync();

}
else
{
//-----------------------------------------------------------------
// If the response is NOT successful return error in ResponseModel
//-----------------------------------------------------------------
ResponseModel<object> errorResponse = new ResponseModel<object>();
errorResponse.Success = false;
errorResponse.Message = $"HttpResponse status was not okay, Message: {responseMessage.ReasonPhrase} -- Code: {responseMessage.StatusCode}";
errorResponse.ResultCode = Constants.RC_HTTP_ERROR;
errorResponse.Data = null;
returnPayload = JsonSerializer.Serialize(errorResponse, _jsonOptions);
}
}
catch (Exception ex)
{
ResponseModel<object> errorResponse = new ResponseModel<object>();
errorResponse.Success = false;
errorResponse.Message = $"Exception sending Message: {ex.Message}";
errorResponse.ResultCode = Constants.RC_HTTP_ERROR;
errorResponse.Data = null;
returnPayload = JsonSerializer.Serialize(errorResponse, _jsonOptions);
}
return returnPayload;
}
#endregion
}
}

Web API

The Web API has a controller that accepts and uploads the file from the console application. This part of the project is ONLY in C#, other languages will be able to talk to this WebAPI.

//-------------------------------------------------------------------------
// 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&lt;System.Byte[]&gt;.</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&lt;HandshakeModel&gt;.</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&lt;System.Byte[]&gt;.</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&lt;System.Byte[]&gt;.</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&lt;System.Byte[]&gt;.</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
}
}

Full File Upload Sample Project

Eclypses has developed full sample projects demonstrating a File Upload using the MTE MKE add-on.

Console Application

Full C# Sample

Full Java Sample

Full Swift Sample

Full Python Sample

Full Go Sample

C# Web API

Full C# WebAPI Sample