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.
- CSharp
- JavaScript
- Java
- Swift
- Python
- Go
//-------------------------------------------------------------------------
// 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<HandshakeResponse>.</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
}
}
/**
* File Upload / Download
*/
const uploadForm = document.getElementById("file-upload-form");
const uploadFileInput = uploadForm.querySelector('input[type="file"]');
const uploadNumberChunksInput = uploadForm.querySelector(
'input[type="number"]',
);
const [uploadFileSize, uploadChunkSize] = uploadForm.querySelectorAll("span");
const uploadBtn = uploadForm.querySelector("button");
const downloadForm = document.getElementById("file-download-form");
const downloadFileName = downloadForm.querySelector("i");
const downloadNumberChunksInput = downloadForm.querySelector(
'input[type="number"]',
);
const downloadBtn = downloadForm.querySelector("button");
/**
* Upload MKE encrypted File
*/
uploadForm.addEventListener("submit", async (e) => {
e.preventDefault();
// show loading message
uploadBtn.innerText = `Uploading...`;
// disable inputs
uploadBtn.setAttribute("disabled", true);
uploadFileInput.setAttribute("disabled", true);
uploadNumberChunksInput.setAttribute("disabled", true);
// read file, calculate chunks
const file = uploadFileInput.files[0];
const numberChunks = uploadNumberChunksInput.value;
const chunkSize = Math.ceil(file.size / numberChunks);
// loop over each chunk, encrypt it, send it
// wait for the previous request to complete before sending the next chunk (synchronous)
const uploadPromises = [];
let i = 0;
for (; i < numberChunks; ++i) {
const currentIndex = i;
// calculate file chunk range
const start = currentIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
// get chunk of file, convert to buffer, then to U8
const fileChunk = file.slice(start, end);
fileChunk.arrayBuffer().then((buffer) => {
const chunkU8 = new Uint8Array(buffer);
// MKE encrypt file
const encryptedU8 = mkeEncryptUint8Array(chunkU8);
const encryptedBlob = new Blob([encryptedU8]);
// create FormData object, attach encrypted file
const formData = new FormData();
formData.append("chunk", encryptedBlob, file.name);
formData.append("currentChunk", currentIndex + 1);
formData.append("totalChunks", numberChunks);
// network request
const promise = fetch(`${DOMAIN}/api/mke/upload`, {
method: "POST",
headers: {
"x-client-id": CLIENT_ID,
},
body: formData,
});
uploadPromises.push(promise);
/**
* After last network request is sent
* wait for all requests to resolve
*/
if (currentIndex + 1 == numberChunks) {
(async function allUploadRequestsSent() {
await Promise.all(uploadPromises).catch((err) =>
console.error("An error occurred while uploading file."),
);
// reset inputs
uploadFileInput.removeAttribute("disabled");
uploadFileInput.value = "";
uploadNumberChunksInput.removeAttribute("disabled");
uploadBtn.removeAttribute("disabled");
uploadBtn.innerText = "Upload";
uploadFileSize.innerText = 0;
uploadChunkSize.innerText = 0;
// enable download form
downloadFileName.innerText = file.name;
downloadNumberChunksInput.removeAttribute("disabled");
downloadBtn.removeAttribute("disabled");
})();
}
});
}
});
/**
* Download MKE Encrypted File
*/
downloadBtn.addEventListener("click", async () => {
// disable inputs/btns
uploadFileInput.setAttribute("disabled", true);
uploadBtn.setAttribute("disabled", true);
downloadBtn.setAttribute("disabled", true);
// collect filename, number of chunks
const filename = downloadFileName.innerText;
const uriEncodedFilename = encodeURIComponent(filename);
// request file size from server, then calculate chunk sizes to request
const sizeResponse = await fetch(
`${DOMAIN}/api/mke/download-size?name=${uriEncodedFilename}`,
{
headers: {
"x-client-id": CLIENT_ID,
},
},
);
if (!sizeResponse.ok) {
throw Error("An unknown error occurred.");
}
const b64 = await sizeResponse.text();
const size = mkeDecryptB64(b64);
// calculate chunk size
const numberChunks = downloadNumberChunksInput.value;
const chunkSize = Math.ceil(size / numberChunks);
downloadBtn.innerText = `Downloading...`;
let i = 0;
const downloadPromises = [];
for (; i < numberChunks; ++i) {
const currentIndex = i;
const downloadPromise = fetch(
`${DOMAIN}/api/mke/download-chunk?name=${uriEncodedFilename}`,
{
headers: {
"x-client-id": CLIENT_ID,
"x-chunk-size": chunkSize,
"x-chunk-index": currentIndex + 1,
},
},
);
downloadPromises.push(downloadPromise);
/**
* After last network request is sent
* wait for all requests to resolve, combine them into single uint8array
*/
if (currentIndex + 1 == numberChunks) {
(async function allUploadRequestsSent() {
const allResponseData = await Promise.all(downloadPromises).catch(
(err) =>
console.error("An error occurred while downloading the file."),
);
let i = 0;
const iMax = allResponseData.length;
let fileData = [];
for (; i < iMax; ++i) {
const networkResponse = allResponseData[i];
const blob = await networkResponse.blob();
const buffer = await blob.arrayBuffer();
const encodedU8 = new Uint8Array(buffer);
const decoded = mkeDecryptUint8Array(encodedU8);
fileData = new Uint8Array([...fileData, ...decoded]);
}
// download file
const anchorTag = window.document.createElement("a");
const ext = filename.split(".").pop();
anchorTag.href = window.URL.createObjectURL(
new Blob([fileData], { type: ext, name: filename }),
);
anchorTag.download = filename;
document.body.appendChild(anchorTag);
anchorTag.click();
window.URL.revokeObjectURL(anchorTag.href);
document.body.removeChild(anchorTag);
// enable inputs/btns
uploadFileInput.removeAttribute("disabled");
uploadBtn.removeAttribute("disabled");
downloadBtn.removeAttribute("disabled");
downloadBtn.innerText = `Download`;
})();
}
}
});
/**
* Update form on file input change
*/
uploadFileInput.addEventListener("input", (e) => {
const file = uploadFileInput.files[0];
const numberChunks = uploadNumberChunksInput.value;
uploadFileSize.innerText = file.size;
uploadChunkSize.innerText = Math.ceil(file.size / numberChunks);
uploadBtn.removeAttribute("disabled");
});
/**
* Update form on chunk input change
*/
uploadNumberChunksInput.addEventListener("input", (e) => {
const file = uploadFileInput.files[0];
if (!file) {
return;
}
const numberChunks = uploadNumberChunksInput.value;
uploadFileSize.innerText = file.size;
uploadChunkSize.innerText = Math.ceil(file.size / numberChunks);
});
/**
* Change domain and clientId when api-select is changed
*/
const apiSelect = document.getElementById("api-select");
apiSelect.addEventListener("input", (e) => {
DOMAIN = e.target.value;
CLIENT_ID = uuidv4();
mtePair();
});
//----------------------------------------------------------------------
// The file structure for the Java project should include the following:
//----------------------------------------------------------------------
// - src
// - com.eclypses.mte (package)
// - uploadClient (package)
// - FileUploader.java
// - package-info.java
// - UploadClient.Models (package)
// - Constants.java
// - HandshakeModel.java
// - HandshakeResponse.java
// - ResponseModel.java
// - module-info.java
// - mtejni.dll
// - Referenced Libraries
// - eclypses-ecdh-1.0.2.jar
// - Maven Dependencies
// - gson-2.9.0.jar
//----------------------------------------------------------------------
// Below only pertenent MTE file code is included
// Links are included at the bottom of this page to download entire project
//-------------------------------------------------------------------------
FileUploader.java
package uploadClient;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;
import com.eclypses.ecdh.EclypsesECDH;
import com.eclypses.mte.*;
import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import uploadClient.Models.*;
public class FileUploader {
// ------------------
// Set Rest API URL
// ------------------
private static String _restAPIName = "https://dev-echo.eclypses.com";
private static String _jsonContentType = "application/json";
// --------------------------------------------------------
// Create gson object that be compatible with C-Sharp json
// --------------------------------------------------------
public static final Gson _gson = new GsonBuilder()
.registerTypeHierarchyAdapter(byte[].class, new ByteArrayToBase64TypeAdapter()).create();
// ------------------------
// Using base64 libraries
// ------------------------
private static class ByteArrayToBase64TypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
return Base64.getDecoder().decode(json.getAsString());
}
public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(Base64.getEncoder().encodeToString(src));
}
}
public static void main(String[] args) throws Exception {
//---------------------------
// set default to NOT use mte
//---------------------------
Boolean useMte = false;
String mteURL = "nomte";
//-----------------
// Buffered input.
//-----------------
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//---------------------------------
// Prompt if we use the MTE or not
//---------------------------------
System.out.print("Would you like to use the MTE? (y/n)");
String mteResponse = br.readLine();
if (mteResponse.equalsIgnoreCase("y")) {
useMte = true;
mteURL = "mte";
}
//-------------------------------
// Perform Handshake if using MTE
//-------------------------------
HandshakeResponse handshake = new HandshakeResponse();
String clientId = UUID.randomUUID().toString();
if (useMte) {
ResponseModel<HandshakeResponse> handshakeResponse = HandshakeWithServer(clientId);
if (!handshakeResponse.Success) {
throw new Exception("Handshake unsuccessful, message:" + handshakeResponse.Message);
}
handshake = handshakeResponse.Data;
}
//---------------------------------------
// Allow multiple uploads till user ends
//---------------------------------------
while (true) {
//----------------------
// Prompt for file path
//----------------------
System.out.print("Enter full path to file to upload: ");
String filename = br.readLine();
File textFile = new File(filename);
String textFileName = textFile.getName();
//---------------
// Set url to API
//---------------
String url = _restAPIName + "/FileUpload/" + mteURL + "?name=" + textFileName;
String charset = "UTF-8";
//---------------------
// Open url connection
//---------------------
URLConnection connection = new URL(url).openConnection();
connection.setDoOutput(true);
connection.setRequestProperty("Accept-Charset", charset);
//------------------------------------------------
// set the content type based on using MTE or not
//------------------------------------------------
String contentType = (useMte) ? "application/octet-stream;charset=" : "text/plain;charset=";
connection.setRequestProperty("Content-Type", contentType + charset);
//-------------------------
// Add client id to header
//-------------------------
connection.setRequestProperty(Constants.ClientIdHeader, clientId);
//---------------------------
// Create MKE Encoder object
//---------------------------
MteMkeEnc mkeEncoder = new MteMkeEnc();
//----------------------------
// If use MTE restore encoder
//----------------------------
if (useMte) {
MteStatus encoderStatus = mkeEncoder.restoreStateB64(handshake.EncoderState);
if (encoderStatus != MteStatus.mte_status_success) {
System.out.println("Error restoring the encoder mte state for Client " + clientId + ": "
+ MteBase.getStatusDescription(encoderStatus));
throw new Exception("Error restoring the encoder mte state for Client " + clientId + ": "
+ MteBase.getStatusDescription(encoderStatus));
}
//-----------------------------
// Initialize chunking session
//-----------------------------
encoderStatus = mkeEncoder.startEncrypt();
if (encoderStatus != MteStatus.mte_status_success) {
throw new Exception("Failed to start encode chunk. Status: " + MteBase.getStatusName(encoderStatus)
+ " / " + MteBase.getStatusDescription(encoderStatus));
}
}
//-----------------
// Set buffer size
//-----------------
byte[] dataBuffer = new byte[1024];
//--------------------
// Set out put stream
//--------------------
try (OutputStream output = connection.getOutputStream();
) {
//--------------------------------
// Write the actual file contents
//--------------------------------
FileInputStream inputStream = new FileInputStream(textFile);
//-----------------------------------------
// read contents of file into input stream
//-----------------------------------------
int bytesRead;
while ((bytesRead = inputStream.read(dataBuffer)) != -1) {
if (useMte) {
//------------------------------------------------------------
// Encode the data in place - encoded data put back in buffer
//------------------------------------------------------------
MteStatus chunkStatus = mkeEncoder.encryptChunk(dataBuffer, 0, bytesRead);
if (chunkStatus != MteStatus.mte_status_success) {
throw new Exception("Failed to encode chunk. Status: " + MteBase.getStatusName(chunkStatus)
+ " / " + MteBase.getStatusDescription(chunkStatus));
}
}
//------------------------
// Write to output writer
//------------------------
output.write(dataBuffer, 0, bytesRead);
//---------------------
// close input stream
//---------------------
inputStream.close();
}
//----------------------
// finalize mte session
//----------------------
if (useMte) {
MteBase.ArrStatus finalEncodedChunk = mkeEncoder.finishEncrypt();
if (finalEncodedChunk.status != MteStatus.mte_status_success) {
throw new Exception("Failed to finish encode chunk. Status: "
+ MteBase.getStatusName(finalEncodedChunk.status) + " / "
+ MteBase.getStatusDescription(finalEncodedChunk.status));
}
//-------------------------------------
// Write final encoded chunk to output
//-------------------------------------
output.write(finalEncodedChunk.arr);
//----------------------------
// save updated encoder state
//----------------------------
handshake.EncoderState = mkeEncoder.saveStateB64();
}
//-------------------------
// close/flush output
//-------------------------
output.flush();
}
//---------------------------------------------------------------
// Request is lazily fired whenever you need to obtain response.
//---------------------------------------------------------------
int responseCode = ((HttpURLConnection) connection).getResponseCode();
//---------------
// Get inStream
//---------------
InputStream in = new BufferedInputStream(connection.getInputStream());
int bytesReadInstream;
ByteArrayOutputStream inBuffer = new ByteArrayOutputStream();
//-----------------------
// Iterate through input
//-----------------------
while ((bytesReadInstream = in.read(dataBuffer, 0, dataBuffer.length)) != -1) {
inBuffer.write(dataBuffer, 0, bytesReadInstream);
}
//--------------------------------
// Flush buffer and set to string
//--------------------------------
inBuffer.flush();
String text = inBuffer.toString();
//-------------------------------
// Deserialize to response model
//-------------------------------
Type serverResponseType = new TypeToken<ResponseModel<byte[]>>() {
}.getType();
ResponseModel<byte[]> serverResponse = _gson.fromJson(text, serverResponseType);
String finalData = "";
//--------------------------------------------------
// If we get a successful response decoder response
//--------------------------------------------------
if (serverResponse.Success) {
//----------------
// Create decoder
//----------------
MteMkeDec mkeDecoder = new MteMkeDec();
if (useMte) {
//-----------------
// Restore Decoder
//-----------------
MteStatus decoderStatus = mkeDecoder.restoreStateB64(handshake.DecoderState);
if (decoderStatus != MteStatus.mte_status_success) {
System.out.println("Error restoring the decoder mte state for Client " + clientId + ": "
+ MteBase.getStatusDescription(decoderStatus));
throw new Exception("Error restoring the decoder mte state for Client " + clientId + ": "
+ MteBase.getStatusDescription(decoderStatus));
}
//------------------------
// Start chunking session
//------------------------
decoderStatus = mkeDecoder.startDecrypt();
if (decoderStatus != MteStatus.mte_status_success) {
throw new Exception(
"Failed to start decode chunk. Status: " + MteBase.getStatusName(decoderStatus) + " / "
+ MteBase.getStatusDescription(decoderStatus));
}
}
if (useMte) {
//-----------------------------------------------
// We know the response is short from the server
// calling decryptChunk only one time then final
//-----------------------------------------------
byte[] decodedBytes = mkeDecoder.decryptChunk(serverResponse.Data);
//-----------------------------
// Finish the chunking session
//-----------------------------
MteBase.ArrStatus finalEncodedChunk = mkeDecoder.finishDecrypt();
if(finalEncodedChunk.status != MteStatus.mte_status_success)
{
throw new Exception("Failed to finish decode chunk. Status: "
+ MteBase.getStatusName(finalEncodedChunk.status)+ " / "
+ MteBase.getStatusDescription(finalEncodedChunk.status));
}
//-----------------------------------------------------------------------
// Check if there is additional bytes if not initialize empty byte array
//-----------------------------------------------------------------------
if(finalEncodedChunk.arr == null) { finalEncodedChunk.arr = new byte[0]; }
//---------------
// Concat bytes
//---------------
byte[] finalBytes = new byte[decodedBytes.length + finalEncodedChunk.arr.length];
System.arraycopy(decodedBytes, 0, finalBytes, 0, decodedBytes.length);
System.arraycopy(finalEncodedChunk.arr, 0, finalBytes, decodedBytes.length, finalEncodedChunk.arr.length);
//------------------------------------
// Convert final byte array to string
//------------------------------------
finalData = new String(finalBytes, StandardCharsets.UTF_8);
//----------------------
// Update Decoder State
//----------------------
handshake.DecoderState = mkeDecoder.saveStateB64();
} else {
//----------------------------------
// else just get the response string
//----------------------------------
finalData = new String(serverResponse.Data, StandardCharsets.UTF_8);
}
} else {
//-----------------------------
// else show the error message
//-----------------------------
finalData = serverResponse.Message;
}
//-----------------------------------
// Output response code and response
//-----------------------------------
System.out.println(responseCode); // Should be 200
System.out.println(finalData);
// ---------------------------------------
// Prompt user to run tasks again or end
// ---------------------------------------
System.out.println("Would you like to upload another file? (y/n)");
String sendAdditional = br.readLine();
if (sendAdditional != null && sendAdditional.equalsIgnoreCase("n")) {
break;
}
}
}
/**
* Handshake with the server and create MTE states
*
* @param clientId --> Current client id
* @param clients --> Client hash map
* @param currentConversation --> current conversation ID
* @return
*/
private static ResponseModel<HandshakeResponse> HandshakeWithServer(String clientId) {
ResponseModel<HandshakeResponse> response = new ResponseModel<HandshakeResponse>();
response.Data = new HandshakeResponse();
try {
System.out.println("Performing Handshake for Client " + clientId);
// --------------------------------
// create clientId for this client
// --------------------------------
HandshakeModel handshake = new uploadClient.Models.HandshakeModel();
handshake.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.getDevicePublicKey();
handshake.ClientDecoderPublicKey = decoderEcdh.getDevicePublicKey();
// -------------------
// Perform handshake
// -------------------
String handshakeString = _gson.toJson(handshake);
String handshakeResponse = MakeHttpCall(_restAPIName + "/api/handshake", "POST",
handshake.ConversationIdentifier, _jsonContentType, handshakeString);
// ---------------------------------------
// Deserialize the result from handshake
// ---------------------------------------
Type handshakeResponseType = new TypeToken<ResponseModel<HandshakeModel>>() {
}.getType();
ResponseModel<HandshakeModel> serverResponse =
_gson.fromJson(handshakeResponse, handshakeResponseType);
// ---------------------------------------
// If handshake was not successful end
// ---------------------------------------
if (!serverResponse.Success) {
response.Message = serverResponse.Message;
response.Success = serverResponse.Success;
response.ResultCode = serverResponse.ResultCode;
System.out.println("Error making DH handshake for Client " + clientId + ": " + serverResponse.Message);
return response;
}
// ----------------------
// Create shared secret
// ----------------------
var encoderSharedSecret = encoderEcdh.createSharedSecret(serverResponse.Data.ClientEncoderPublicKey);
var decoderSharedSecret = decoderEcdh.createSharedSecret(serverResponse.Data.ClientDecoderPublicKey);
// ----------------------------------------------------------
// Clear container to ensure key is different for each client
// ----------------------------------------------------------
encoderEcdh = null;
decoderEcdh = null;
// --------------------
// Create MTE Encoder
// --------------------
MteEnc encoder = new MteEnc();
encoder.setEntropy(encoderSharedSecret);
encoder.setNonce(Long.parseLong(serverResponse.Data.Timestamp));
MteStatus status = encoder.instantiate(clientId);
if (status != MteStatus.mte_status_success) {
response.Message = "Error creating encoder: Status: " + MteBase.getStatusName(status) + " / "
+ MteBase.getStatusDescription(status);
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.Success = false;
System.out.println(response.Message);
return response;
}
// ------------------------
// Get Encoder State
// ------------------------
response.Data.EncoderState = encoder.saveStateB64();
// --------------------
// Create MTE decoder
// --------------------
MteDec decoder = new MteDec();
decoder.setEntropy(decoderSharedSecret);
decoder.setNonce(Long.parseLong(serverResponse.Data.Timestamp));
status = decoder.instantiate(clientId);
if (status != MteStatus.mte_status_success) {
response.Message = "Error creating decoder: Status: " + MteBase.getStatusName(status) + " / "
+ MteBase.getStatusDescription(status);
response.ResultCode = Constants.RC_MTE_DECODE_EXCEPTION;
response.Success = false;
System.out.println(response.Message);
return response;
}
// ------------------------
// Set MTE decoder state
// ------------------------
response.Data.DecoderState = decoder.saveStateB64();
} catch (Exception ex) {
ex.printStackTrace();
response.Message = "Exception during handshake: " + ex.getMessage();
response.Success = false;
response.ResultCode = Constants.RC_HANDSHAKE_EXCEPTION;
throw ex;
}
return response;
}
/**
* Make the Http Call
*
* @param connectionUrl --> Connection URL
* @param connectionMethod --> Connection Method
* @param clientId --> Current client id
* @param contentType --> http content type
* @param payload --> http call payload
* @return
*/
private static String MakeHttpCall(String connectionUrl, String connectionMethod, String clientId,
String contentType, String payload) {
String returnPayload = "";
try {
// -----------------------------------------
// Set URI and other default Http settings
// -----------------------------------------
URL url = new URL(connectionUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// ---------------------------
// Check if we have a payload
// ---------------------------
if (payload != null && !payload.isEmpty()) {
conn.setDoOutput(true);
}
conn.setRequestMethod(connectionMethod);
conn.setRequestProperty("Content-Type", contentType);
// -------------------------
// Add client id to header
// -------------------------
conn.setRequestProperty(Constants.ClientIdHeader, clientId);
OutputStream os = conn.getOutputStream();
os.write(payload.getBytes());
os.flush();
// --------------------------------------------------
// If we did not get a good connection return error
// --------------------------------------------------
if (conn.getResponseCode() < HttpURLConnection.HTTP_OK || conn.getResponseCode() > 299) {
// ------------------------------------------------------
// Create error response to send back to calling method
// ------------------------------------------------------
ResponseModel<Object> errorResponse = new ResponseModel<Object>();
errorResponse.Data = null;
errorResponse.Message = "Failed : HTTP error code : " + conn.getResponseCode();
errorResponse.ResultCode = Constants.RC_HTTP_ERROR;
returnPayload = _gson.toJson(errorResponse);
return returnPayload;
}
// -------------------------
// Read in the inputStream
// -------------------------
BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));
// --------------------
// Assign the payload
// --------------------
returnPayload = br.readLine();
// ---------------------------
// Disconnect the connection
// ---------------------------
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
ResponseModel<Object> errorResponse = new ResponseModel<Object>();
errorResponse.Data = null;
errorResponse.Message = "MalformedURLException in MakeHtteCall : " + e.getMessage();
errorResponse.ResultCode = Constants.RC_HTTP_EXCEPTION;
returnPayload = _gson.toJson(errorResponse);
} catch (IOException e) {
e.printStackTrace();
ResponseModel<Object> errorResponse = new ResponseModel<Object>();
errorResponse.Data = null;
errorResponse.Message = "IOException in MakeHttpCall : " + e.getMessage();
errorResponse.ResultCode = Constants.RC_HTTP_EXCEPTION;
returnPayload = _gson.toJson(errorResponse);
}
return returnPayload;
}
}
//-------------------------------------------------------------------------
// The file structure for the Swift Xcode project should include the following:
//-------------------------------------------------------------------------
// - main.swift
// - FileStreamUpload.swift
// - AppSettings.swift
// - MobyDickeBook.txt
// - MteFileUploads.entitlements
// - Models (directory)
// - Constants.swift
// - FileUploadRequest.swift
// - PairRequest.swift
// - PairResponse.swift
// - ConnectionModel.swift
// - APIResult.swift
// - Helpers (directory)
// - EcdhHelper.swift
// - MTEHelper.swift
// - WebHelper.swift
// - JsonHelper.swift
// - PackageCSharpECDH(1.0.1)
//-------------------------------------------------------------------------
// Below only pertinent MTE file code is included
// Links are included at the bottom of this page to download entire project
//-------------------------------------------------------------------------
main.swift
import Foundation
let _jsonEncoder = getJSONEncoder()
let _jsonDecoder = getJSONDecoder()
print("****** Simple File Upload Console Demo ******\n")
enum PairType: String {
case encoder = "Enc"
case decoder = "Dec"
}
extension Data {
var bytes : [UInt8]{
return [UInt8](self)
}
}
extension String: LocalizedError {
public var errorDescription: String? { return self }
}
let main = Main()
RunLoop.current.run()
class Main: StreamUploadDelegate, MteEntropyCallback, MteNonceCallback {
var mteHelper: MTEHelper!
var fileStreamUpload: FileStreamUpload!
var pairType: PairType!
var tempEntropy = [UInt8]()
init() {
Settings.clientId = UUID().uuidString.lowercased()
// Instantiate FileStreamUpload class
fileStreamUpload = FileStreamUpload()
fileStreamUpload.streamUploadDelegate = self
// Instantiate the MTEHelper class
do {
mteHelper = try MTEHelper()
mteHelper.mteEntropyCallback = self
mteHelper.mteNonceCallback = self
} catch {
print("Unable to Instantiate MteHelper. Error: \(error.localizedDescription)")
exit(EXIT_FAILURE)
}
pairWithServer()
uploadStream("MobyDickeBook.txt")
}
func pairWithServer() {
do {
try instantiateEncoder()
try instantiateDecoder()
} catch {
print(error.localizedDescription)
exit(EXIT_FAILURE)
}
}
fileprivate func instantiateEncoder() throws {
pairType = .encoder
mteHelper.encoderPersonalizationString = UUID().uuidString.lowercased()
try mteHelper.createEncoder()
}
fileprivate func instantiateDecoder() throws {
pairType = .decoder
mteHelper.decoderPersonalizationString = UUID().uuidString.lowercased()
try mteHelper.createDecoder()
}
func uploadStream(_ filename: String) {
let filePath = FileManager.default.currentDirectoryPath + "/\(filename)"
guard let urlEncodedFilename = filename.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
print("Unable to urlEncode \(filename)")
exit(EXIT_FAILURE)
}
let connectionModel = ConnectionModel(url: Settings.serverUrl,
method: Constants.POST,
route: "api/mke/uploadstream?name=\(urlEncodedFilename)",
payload: nil,
contentType: "text/plain; charset=utf-8",
clientId: Settings.clientId,
mteVersion: MteBase.getVersion())
fileStreamUpload.upload(connectionModel: connectionModel, filePath: filePath, mteHelper: mteHelper)
}
// MARK: Stream Upload Delegates
func didUploadToServer(success: Bool, filename: String) {
print("\nServer reports that the upload of \(filename) was successful.")
exit(EXIT_SUCCESS)
}
func uploadDidFail(message: String) {
print("Server reports that the upload Failed. Error: \(message)")
exit(EXIT_FAILURE)
}
//MARK: Callbacks
func entropyCallback(_ minEntropy: Int,
_ minLength: Int,
_ maxLength: UInt64,
_ entropyInput: inout [UInt8],
_ eiBytes: inout UInt64,
_ entropyLong: inout UnsafeMutableRawPointer?) -> mte_status {
var myEntropyRaw: UnsafeMutableRawPointer? = nil
// We have a network call to get entropy so we have to block this thread until it returns.
// There is likely a better solution.
let semaphore = DispatchSemaphore(value: 0)
// entropyCallback function does not support concurrency so
// we will call the server in a Task to exchange ECDH public keys
Task.init {
try await getEntropy()
semaphore.signal()
}
let timeoutResult = semaphore.wait(timeout: .now() + 15)
if timeoutResult == .timedOut {
print("Timeout in getEntropy call for \(pairType.rawValue)")
}
if tempEntropy.count < minLength {
print("mte_status_drbg_catastrophic")
}
// Set the actual entropy length. It cannot exceed the maximum required.
let buffBytes = eiBytes
eiBytes = min(UInt64(tempEntropy.count), maxLength)
// If the length is greater than the length of the provided buffer, we have
// to create our own buffer instead.
if eiBytes > buffBytes {
// Get the entropy input as an array.
let ei = tempEntropy
// If there is previous raw entropy, deallocate it.
if myEntropyRaw != nil {
myEntropyRaw!.deallocate()
}
// Allocate unsafe memory for the entropy.
myEntropyRaw =
UnsafeMutableRawPointer.allocate(byteCount: ei.count, alignment: 16)
// Copy from the entropy array to the unsafe memory.
ei.withUnsafeBytes { buff in
let raw = myEntropyRaw!.assumingMemoryBound(to: UInt8.self)
let ei = buff.bindMemory(to: UInt8.self)
raw.assign(from: ei.baseAddress!, count: ei.count)
}
// Set the raw pointer to point at the unsafe memory.
entropyLong = myEntropyRaw
}
else {
// Copy the entropy to the buffer.
entropyInput.replaceSubrange(Range(uncheckedBounds: (0, Int(eiBytes))),
with: tempEntropy.prefix(Int(eiBytes)))
tempEntropy.resetBytes(in: 0..<tempEntropy.count)
}
// Success.
return mte_status_success
}
private func getEntropy() async throws {
var personalizationString: String!
switch pairType {
case .decoder:
personalizationString = mteHelper.decoderPersonalizationString
default:
personalizationString = mteHelper.encoderPersonalizationString
}
let ecdh = try EcdhHelper(name: "\(pairType.rawValue) Entropy")
let publicKey = try ecdh.getPublicKey()
// Send the public keys and IDs to the Server
let request = PairRequest(personalizationString: personalizationString,
publicKey: publicKey,
pairType: pairType.rawValue)
let payload = try _jsonEncoder.encode(request)
let connectionModel = ConnectionModel(url: Settings.serverUrl,
method: Constants.POST,
route: "api/pairone",
payload: payload,
contentType: "application/json; charset=utf-8",
clientId: Settings.clientId,
mteVersion: MteBase.getVersion())
let result = await WebService.call(connectionModel: connectionModel)
switch result {
case .failure(let code, let message):
print("Could not pair with Server. ErrorCode: \(code), ErrorMessage: \(message).")
exit(EXIT_FAILURE)
case .success(let data):
let response = try _jsonDecoder.decode(PairResponse.self, from: data)
try ecdh.createSharedSecret(remotePublicKeyStr: response.publicKey,
entropy: &tempEntropy)
switch self.pairType {
case .decoder:
self.mteHelper.decoderNonce = UInt64(response.timestamp)!
default:
self.mteHelper.encoderNonce = UInt64(response.timestamp)!
}
}
}
func nonceCallback(_ minLength: Int, _ maxLength: Int, _ nonce: inout [UInt8], _ nBytes: inout Int) {
var nCopied: Int = 0
switch pairType {
case .decoder:
// Copy the nonce in little-endian format to the nonce buffer.
nCopied = min(nonce.count, MemoryLayout.size(ofValue: mteHelper.decoderNonce))
for i in 0..<nCopied {
nonce[i] = UInt8(UInt64(mteHelper.decoderNonce >> (i * 8)) & 0xFF)
}
mteHelper.decoderNonce = 0
default:
// Copy the nonce in little-endian format to the nonce buffer.
nCopied = min(nonce.count, MemoryLayout.size(ofValue: mteHelper.encoderNonce))
for i in 0..<nCopied {
nonce[i] = UInt8(UInt64(mteHelper.encoderNonce >> (i * 8)) & 0xFF)
}
mteHelper.encoderNonce = 0
}
// If the minimum length is greater than the size of the nonce we got, fill
// up to that length with zeros.
if nCopied < minLength {
for i in nCopied..<minLength {
nonce[i] = 0
}
nBytes = minLength
}
else {
nBytes = nCopied
}
}
}
EcdhHelper.swift
import Foundation
import CryptoKit
public class EcdhHelper {
private var sePrivateKey: SecureEnclave.P256.KeyAgreement.PrivateKey!
private var privateKey: P256.KeyAgreement.PrivateKey!
private var remotePublicKey: P256.KeyAgreement.PublicKey!
private var name: String
public init(name: String) throws {
// We use the 'name' param just for debugPrint.
self.name = name
// Create privateKey using Secure Enclave if it's available and we aren't on a simulator
if SecureEnclave.isAvailable && TARGET_OS_SIMULATOR != 1 {
do {
sePrivateKey = try SecureEnclave.P256.KeyAgreement.PrivateKey()
} catch {
debugPrint("Unable to create private Key with Secure Enclave. Error: \(error.localizedDescription)")
throw ECDHErrors.unableToInitializeEcdhHelper
}
} else {
privateKey = P256.KeyAgreement.PrivateKey()
}
debugPrint("EcdhHelper has been instantiated for \(name)")
}
deinit {
// This is included primarily to demonstrate the short lifecycle of this class
debugPrint("EcdhHelper has been destroyed for \(name)")
}
public func getPublicKey() throws -> String {
var publicKeyData = Data()
if SecureEnclave.isAvailable && TARGET_OS_SIMULATOR != 1 {
publicKeyData = sePrivateKey.publicKey.derRepresentation
} else {
publicKeyData = privateKey.publicKey.derRepresentation
}
return publicKeyData.base64EncodedString()
}
public func createSharedSecret(remotePublicKeyStr: String, entropy: inout [UInt8]) throws {
do {
try setRemotePublicKey(keyString: remotePublicKeyStr)
var sharedSecret: SharedSecret
// create the shared secret
if SecureEnclave.isAvailable && TARGET_OS_SIMULATOR != 1 {
sharedSecret = try sePrivateKey.sharedSecretFromKeyAgreement(with: remotePublicKey)
} else {
sharedSecret = try privateKey.sharedSecretFromKeyAgreement(with: remotePublicKey)
}
// because C# does it this way, we'll grab the shared secret data and hash it,
// then convert it to a [UInt8] to use as entropy
var sharedSecretData = sharedSecret.withUnsafeBytes {Data(Array($0))}
let sharedSecretDataHash = SHA256.hash(data: sharedSecretData)
entropy = sharedSecretDataHash.withUnsafeBytes {Data(Array($0))}.bytes
// While not strictly necessary because this class is about to be destroyed, we'll zero out the 'sensitive' data.
// entropy byte array will be zeroized immediately after using it.
sharedSecretData.resetBytes(in: 0...sharedSecretData.count-1)
var hashData = sharedSecretDataHash.withUnsafeBytes {Data(Array($0))}
hashData.resetBytes(in: 0...hashData.count-1)
} catch {
debugPrint("Unable to create Shared Secret. Error: \(error.localizedDescription)")
throw ECDHErrors.unableToCreateSharedSecret
}
}
private func setRemotePublicKey(keyString: String) throws {
do {
guard let publicKeyData = Data(base64Encoded: keyString) else {
throw ECDHErrors.unableToCreateRemotePublicKeyData
}
remotePublicKey = try P256.KeyAgreement.PublicKey(derRepresentation: publicKeyData)
} catch {
debugPrint("Unable to create Remote Public Key with Secure Enclave. Error: \(error.localizedDescription)")
throw ECDHErrors.unableToCreateRemotePublicKey
}
}
}
enum ECDHErrors: Error {
case unableToInitializeEcdhHelper
case unableToCreateLocalPublicKey
case unableToCreateRemotePublicKeyData
case unableToCreateRemotePublicKey
case unableToCreateSharedSecret
var resultCode: String {
switch self {
case .unableToInitializeEcdhHelper:
return "Unable to Initialize an Elliptic Curve Diffie-Hellman Helper. Unable to Continue."
case .unableToCreateLocalPublicKey:
return "Unable to Create an Elliptic Curve Diffie-Hellman Public Key with Secure Enclave for this Device. Unable to Continue."
case .unableToCreateRemotePublicKeyData:
return "Unable to convert the provided string to Public Key data. Unable to Continue."
case .unableToCreateRemotePublicKey:
return "Unable to Create an Elliptic Curve Diffie-Hellman Public Key using Secure Enclave with public key from the remote endpoint. Unable to Continue."
case .unableToCreateSharedSecret:
return "Unable to Create a Elliptic Curve Diffie-Hellman Shared Secret. Unable to Continue."
}
}
}
MteHelper.swift
import Foundation
enum EncoderType {
case mte
case mke
case flen
}
enum DecoderType {
case mte
case mke
// No flen Decoder type necessary
}
// MTE Defaults
let defEncType = EncoderType.mte
let defDecType = DecoderType.mte
let defFixLen = 100
class MTEHelper {
var mteEntropyCallback: MteEntropyCallback!
var mteNonceCallback: MteNonceCallback!
// Set up some class variables we will be needing
private var encoderState: [UInt8]!
private var decoderState: [UInt8]!
var encoderEntropy = [UInt8](repeating: 0, count: 32)
var encoderNonce: UInt64 = 0
var encoderPersonalizationString: String!
var decoderEntropy = [UInt8](repeating: 0, count: 32)
var decoderNonce: UInt64 = 0
var decoderPersonalizationString: String!
// Encoders
private var mteEncoder: MteEnc!
private var mkeEncoder: MteMkeEnc!
private var flenEncoder: MteFlenEnc!
// Decoders
private var mteDecoder: MteDec!
private var mkeDecoder: MteMkeDec!
// no flen decoder is necessary
// MARK: init
init() throws {
// Get version of MTE we are using
let mteVersion: String = MteBase.getVersion()
debugPrint("Using MTE Version \(mteVersion)")
// Check mte license
if !MteBase.initLicense(Settings.licCompanyName, Settings.licCompanyKey) {
throw "License Check failed."
}
}
// MARK: CreateEncoder
func createEncoder() throws {
let encoder = try MteEnc()
try instantiateEncoder(encoder: encoder)
}
func createEncoder(drbg: mte_drbgs,
tokBytes: Int,
verifiers: mte_verifiers) throws {
let encoder = try MteEnc(drbg,
tokBytes,
verifiers)
try instantiateEncoder(encoder: encoder)
}
private func instantiateEncoder(encoder: MteEnc) throws {
var status = mte_status_success
encoder.setEntropyCallback(mteEntropyCallback)
encoder.setNonceCallback(mteNonceCallback)
// Instantiate MTE encoder
status = encoder.instantiate(encoderPersonalizationString)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
// print("EncoderState: \(encoder.saveStateB64()!)") // Uncomment to confirm encoder state value and
// and compare with Server decoder state. This is a particularly useful debugging tool.
encoderState = encoder.saveState()
status = encoder.uninstantiate()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
}
// MARK: CreateDecoder
func createDecoder() throws {
let decoder = try MteDec()
try instantiateDecoder(decoder: decoder)
}
func createDecoder(timestampWindow: UInt64,
sequenceWindow: Int) throws {
let decoder = try MteDec(timestampWindow, sequenceWindow)
try instantiateDecoder(decoder: decoder)
}
func createDecoder(drbg: mte_drbgs,
tokBytes: Int,
verifiers: mte_verifiers,
timestampWindow: UInt64,
sequenceWindow: Int) throws {
let decoder = try MteDec(drbg,
tokBytes,
verifiers,
timestampWindow,
sequenceWindow)
try instantiateDecoder(decoder: decoder)
}
private func instantiateDecoder(decoder: MteDec) throws {
var status = mte_status_success
decoder.setEntropyCallback(mteEntropyCallback)
decoder.setNonceCallback(mteNonceCallback)
// Instantiate MTE encoder
status = decoder.instantiate(decoderPersonalizationString)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
// print("DecoderState: \(decoder.saveStateB64()!)") // Uncomment to confirm decoder state value and
// and compare with Server encoder state. This is a particularly useful debugging tool.
decoderState = decoder.saveState()
status = decoder.uninstantiate()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
}
// MARK: Encode
func encode(encoderType: EncoderType = defEncType, message: String, fixedLength: Int = defFixLen) throws -> String {
var encodeResult: (encoded: String, status: mte_status)!
var status: mte_status!
switch encoderType {
case .mte:
let encoder = try MteEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encodeB64(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
case .mke:
let encoder = try MteMkeEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encodeB64(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
case .flen:
let encoder = try MteFlenEnc(fixedLength)
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encodeB64(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
}
if encodeResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return encodeResult.encoded
}
func encode(encoderType: EncoderType = defEncType, message: [UInt8]) throws -> [UInt8] {
var encodeResult: (encoded: ArraySlice<UInt8>, status: mte_status)!
var status: mte_status!
switch encoderType {
case .mte:
let encoder = try MteEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encode(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
case .mke:
let encoder = try MteMkeEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encode(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
case .flen:
let encoder = try MteMkeEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
encodeResult = encoder.encode(message)
encoderState = encoder.saveState()
status = encoder.uninstantiate()
}
if encodeResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return Array(encodeResult.encoded)
}
// MARK: Decode
func decode(decoderType: DecoderType = defDecType, encoded: String) throws -> String {
var decodeResult: (str: String, status: mte_status)!
var status: mte_status!
switch decoderType {
case .mte:
let decoder = try MteDec()
status = decoder.restoreState(decoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
decodeResult = decoder.decodeStrB64(encoded)
decoderState = decoder.saveState()
status = decoder.uninstantiate()
case .mke:
let decoder = try MteMkeDec()
status = decoder.restoreState(decoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
decodeResult = decoder.decodeStrB64(encoded)
decoderState = decoder.saveState()
status = decoder.uninstantiate()
}
if decodeResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return decodeResult.str
}
func decode(decoderType: DecoderType = defDecType, encoded: [UInt8]) throws -> [UInt8] {
var decodeResult: (decoded: ArraySlice<UInt8>, status: mte_status)!
var status: mte_status!
switch decoderType {
case .mte:
let decoder = try MteDec()
status = decoder.restoreState(decoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
decodeResult = decoder.decode(encoded)
decoderState = decoder.saveState()
status = decoder.uninstantiate()
case .mke:
let decoder = try MteMkeDec()
status = decoder.restoreState(decoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
decodeResult = decoder.decode(encoded)
decoderState = decoder.saveState()
status = decoder.uninstantiate()
}
if decodeResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return Array(decodeResult.decoded)
}
// MARK: Chunking
func startEncrypt() throws -> MteMkeEnc {
var status: mte_status!
let encoder = try MteMkeEnc()
status = encoder.restoreState(encoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
status = encoder.startEncrypt()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return encoder
}
func encryptChunk(encoder: MteMkeEnc, buffer: inout [UInt8]) throws {
let status = encoder.encryptChunk(&buffer)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
}
func finishEncrypt(encoder: MteMkeEnc) throws -> [UInt8] {
let encryptFinishResult = encoder.finishEncrypt()
if encryptFinishResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: encryptFinishResult.status))"
}
encoderState = encoder.saveState()
let status = encoder.uninstantiate()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return Array(encryptFinishResult.encoded)
}
func startDecrypt() throws -> MteMkeDec {
var status: mte_status!
let decoder = try MteMkeDec()
status = decoder.restoreState(decoderState)
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
status = decoder.startDecrypt()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return decoder
}
func decryptChunk(decoder: MteMkeDec, data: Data) throws -> [UInt8] {
let decodeResult = decoder.decryptChunk(data.bytes)
if decodeResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: decodeResult.status))"
}
return Array(decodeResult.data)
}
func finishDecrypt(decoder: MteMkeDec) throws -> [UInt8] {
let finishDecryptResult = decoder.finishDecrypt()
if finishDecryptResult.status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: finishDecryptResult.status))"
}
decoderState = decoder.saveState()
let status = decoder.uninstantiate()
if status != mte_status_success {
throw "\(#function) error: \(resolveErrorMessage(status: status))"
}
return Array(finishDecryptResult.data)
}
func restoreEncoderState(state: [UInt8]) {
encoderState = state
}
func restoreDecoderState(state: [UInt8]) {
decoderState = state
}
func getEncoderState(state: inout [UInt8]) {
state = encoderState
}
func getDecoderState(state: inout [UInt8]) {
state = decoderState
}
private func resolveErrorMessage(status: mte_status) -> String {
return "Status: \(MteBase.getStatusName(status)). Description: \(MteBase.getStatusDescription(status))"
}
}
FileStreamUpload.swift
import Foundation
protocol StreamUploadDelegate: AnyObject {
func didUploadToServer(success: Bool, filename: String)
func uploadDidFail(message: String)
}
class FileStreamUpload: NSObject, URLSessionDelegate, StreamDelegate, URLSessionStreamDelegate, URLSessionDataDelegate {
weak var streamUploadDelegate: StreamUploadDelegate?
var fileHandle: FileHandle!
var mteHelper: MTEHelper!
lazy var session: URLSession = URLSession(configuration: .default,
delegate: self,
delegateQueue: .main)
struct Streams {
let input: InputStream
let output: OutputStream
}
lazy var boundStreams: Streams = {
var inputOrNil: InputStream? = nil
var outputOrNil: OutputStream? = nil
Stream.getBoundStreams(withBufferSize: Settings.chunkSize,
inputStream: &inputOrNil,
outputStream: &outputOrNil)
guard let input = inputOrNil, let output = outputOrNil else {
fatalError("On return of `getBoundStreams`, both `inputStream` and `outputStream` will contain non-nil streams.")
}
// configure and open output stream
output.delegate = self
output.schedule(in: .current, forMode: .default)
output.open()
return Streams(input: input, output: output)
}()
func upload(connectionModel: ConnectionModel, filePath: String, mteHelper: MTEHelper) {
self.mteHelper = mteHelper
guard let fileUrl = URL(string: filePath) else {
return
}
if FileManager.default.fileExists(atPath: fileUrl.path) {
do {
fileHandle = try FileHandle(forReadingFrom: fileUrl)
} catch {
print("Unable to read from file. Error: \(error.localizedDescription)")
}
}
let url = URL(string: String(format: "%@%@", connectionModel.url, connectionModel.route))
var request = URLRequest(url: url!,
cachePolicy: .reloadIgnoringLocalCacheData,
timeoutInterval: 10)
request.httpMethod = connectionModel.method
request.httpBody = connectionModel.payload
request.setValue(connectionModel.contentType, forHTTPHeaderField: "Content-Type")
request.setValue(connectionModel.clientId, forHTTPHeaderField: "x-client-id")
request.setValue(connectionModel.mteVersion, forHTTPHeaderField: "x-mte-version")
session.uploadTask(withStreamedRequest: request).resume()
}
func urlSession(_ session: URLSession, task: URLSessionTask, needNewBodyStream completionHandler: @escaping (InputStream?) -> Void) {
completionHandler(boundStreams.input)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
// We'll convert the response data to a String
let dataStr = String(decoding: data, as: UTF8.self)
// and notify via delegate
streamUploadDelegate?.didUploadToServer(success: true, filename: dataStr)
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
guard aStream == boundStreams.output else {
return
}
var buffer = [UInt8]()
if eventCode.contains(.hasSpaceAvailable) {
do {
let encoder = try mteHelper.startEncrypt()
buffer = [UInt8](fileHandle.readData(ofLength: Settings.chunkSize))
while !(buffer.isEmpty) {
try mteHelper.encryptChunk(encoder: encoder, buffer: &buffer)
self.boundStreams.output.write(buffer, maxLength: buffer.count)
// read the next chunk
buffer = [UInt8](fileHandle.readData(ofLength: Settings.chunkSize))
}
let finalBuffer = try mteHelper.finishEncrypt(encoder: encoder)
self.boundStreams.output.write(finalBuffer, maxLength: finalBuffer.count)
self.boundStreams.output.close()
} catch {
print("Upload failed. Error: \(error.localizedDescription)")
}
}
if eventCode.contains(.errorOccurred) {
print("Upload error occurred. Closing Streams. Error: \(eventCode.rawValue)")
streamUploadDelegate?.uploadDidFail(message: "Upload to Server failed.")
self.boundStreams.output.close()
self.boundStreams.input.close()
do {
try fileHandle.close()
} catch {
print("Unable to Close fileHandle. Error: \(error.localizedDescription)")
}
}
if eventCode.contains(.endEncountered) {
print("EndOfStream encountered")
do {
self.boundStreams.input.close()
try fileHandle.close()
} catch {
print("Unable to Close fileHandle. Error: \(error.localizedDescription)")
}
}
}
}
#----------------------------------------------------------------------
# The file structure for the Python project should include the following:
#----------------------------------------------------------------------
# - all python source code files from the sdk (src/py)
# - the MTE library (mte.dll, libmte.so, or libmte.dylib depending on platform)
# - EclypsesECDH (package)
# - EclypsesECDH.py
# - Program.py
# - UploadFile.py
# - UploadResponse.py
# - ResponseModel.py
# - HandshakeModel.py
# - HandshakeResponse.py
# - Constants.py
#----------------------------------------------------------------------
# These should all be placed within the same directory.
# Below only pertenent MTE file code is included
# Links are included at the bottom of this page to download entire project
#-------------------------------------------------------------------------
Program.py
#!/usr/bin/env python3
import sys
import uuid
from asyncio.windows_events import NULL
from tkinter import filedialog as fd, Tk as tk
from HandshakeResponse import HandshakeResponse
from UploadFile import UploadFile
from MteMkeEnc import MteMkeEnc
from MteMkeDec import MteMkeDec
# Create default Encoder and Decoder.
client_encoder = MteMkeEnc.fromdefault()
client_decoder = MteMkeDec.fromdefault()
# Set if using MTE.
use_mte = True
def main():
# Initialize Encoder and Decoder and set client_id.
handshake = HandshakeResponse()
handshake.encoder_state = ""
handshake.decoder_state = ""
client_id = str(uuid.uuid4())
# Handshake with server and create MTE.
handshake_response = UploadFile().handshake_with_server(client_id)
if not handshake_response.success:
raise Exception("Error trying to handshake with server: {0}\n".format(
handshake_response.message))
# Set Decoder and Encoder state.
handshake.decoder_state = handshake_response.data.decoder_state
handshake.encoder_state = handshake_response.data.encoder_state
# Allow file upload until user selects to end.
while True:
# Prompt user for file to upload.
path = ""
try:
# Fix foregrounding window issue
dialog = tk()
dialog.withdraw()
dialog.wm_attributes('-topmost', 1)
path = fd.askopenfilename(parent=dialog)
finally:
dialog.destroy()
# If the path is empty, the dialog was cancelled.
if not path:
break
# Send file to server.
upload_response = UploadFile().send(
path, use_mte, handshake.encoder_state, handshake.decoder_state, client_id)
# If unsuccessful, then end.
if not upload_response.success:
raise Exception("Error uploading file: {0}\n".format(
upload_response.message))
print(upload_response.data.server_response)
# Update Encoder and Decoder states to be the latest state.
handshake.encoder_state = upload_response.data.encoder_state
handshake.decoder_state = upload_response.data.decoder_state
# Prompt to upload another file.
print("Would you like to upload an additional file (y/n)?")
send_additional = input()
if send_additional != NULL and send_additional.lower().startswith("n"):
break
return 0
if __name__ == "__main__":
sys.exit(main())
UploadFile.py
#!/usr/bin/env python3
import requests
import json
import os
import base64
from asyncio.windows_events import NULL
from Constants import Constants
from HandshakeModel import HandshakeModel
from ResponseModel import ResponseModel
from HandshakeResponse import HandshakeResponse
from UploadResponse import UploadResponse
from EclypsesECDH import EclypsesECDH
from MteMkeEnc import MteMkeEnc
from MteMkeDec import MteMkeDec
from MteStatus import MteStatus
class UploadFile:
def __init__(self):
# Set upload file constants.
self.MAX_CHUNK_SIZE = 1024
# Upload file properties.
self.web_request = ""
self.file_reader = ""
self.request_stream = ""
def handshake_with_server(self, client_id) -> ResponseModel:
"""
Handshakes with the server.
"""
response = ResponseModel()
# Create client_id for this client.
handshake = HandshakeModel()
handshake.conversation_identifier = client_id
# Create Eclypses DH containers for handshake.
encoder_ecdh = EclypsesECDH()
decoder_ecdh = EclypsesECDH()
# Get the public key to send to other side.
handshake.client_encoder_public_key = encoder_ecdh.get_device_public_key()
handshake.client_decoder_public_key = decoder_ecdh.get_device_public_key()
# Perform handshake.
url = Constants().REST_API_NAME + "/api/handshake"
payload = {
"Timestamp": "null",
"ConversationIdentifier": handshake.conversation_identifier,
"ClientEncoderPublicKey": handshake.client_encoder_public_key.decode(),
"ClientDecoderPublicKey": handshake.client_decoder_public_key.decode()
}
headers = {
"Content-Type": "application/json",
"accept": "*/*",
Constants().CLIENT_ID_HEADER: handshake.conversation_identifier
}
api_handshake_response = requests.post(
url=url, data=json.dumps(payload), headers=headers)
# Deserialize the result from handshake.
server_response = api_handshake_response.json()
# If handshake was not successful, then break.
if server_response['Success'] == False:
print("Error making DH handshake for Client {0}: {1}".format(
client_id, server_response['Message']))
response.data = server_response['Data']
response.message = server_response['Message']
response.success = server_response['Success']
response.result_code = server_response['ResultCode']
response.access_token = server_response['access_token']
response.exception_uid = server_response['ExceptionUid']
return response
# Create shared secret.
encoder_shared_secret_model = encoder_ecdh.create_shared_secret(
bytes(server_response['Data']['ClientEncoderPublicKey'], 'utf-8'))
decoder_shared_secret_model = decoder_ecdh.create_shared_secret(
bytes(server_response['Data']['ClientDecoderPublicKey'], 'utf-8'))
# Set MTE settings and get state.
# Get the nonce from the timestamp.
nonce = int(server_response['Data']['Timestamp'])
response.data = HandshakeResponse()
# Set Encoder and then save state.
encoder = MteMkeEnc.fromdefault()
encoder.set_entropy(base64.b64decode(encoder_shared_secret_model))
encoder.set_nonce(nonce)
status = encoder.instantiate(handshake.conversation_identifier)
if status != MteStatus.mte_status_success:
response.success = False
response.message = "Failed to initialize the MTE Encoder engine. Status {0} / {1}".format(
encoder.get_status_name(status), encoder.get_status_description(status))
response.result_code = Constants().RC_MTE_STATE_CREATION
return response
response.data.encoder_state = encoder.save_state_b64()
# Set Decoder and then save state.
decoder = MteMkeDec.fromdefault()
decoder.set_entropy(base64.b64decode(decoder_shared_secret_model))
decoder.set_nonce(nonce)
status = decoder.instantiate(handshake.conversation_identifier)
if status != MteStatus.mte_status_success:
response.success = False
response.message = "Failed to initialize the MTE Decoder engine. Status {0} / {1}".format(
decoder.get_status_name(status), decoder.get_status_description(status))
response.result_code = Constants().RC_MTE_STATE_CREATION
return response
response.data.decoder_state = decoder.save_state_b64()
return response
def send(self, path, use_mte, encoder_state, decoder_state, client_id) -> ResponseModel:
"""
Sends the file up to the server.
"""
upload_response = ResponseModel()
upload_response.data = UploadResponse()
upload_response.data.encoder_state = encoder_state
upload_response.data.decoder_state = decoder_state
# Create default Encoder.
encoder = MteMkeEnc.fromdefault()
# Get file info and create url.
url_type = "mte" if use_mte else "nomte"
file_url = os.path.join(Constants().REST_API_NAME + "/FileUpload/",
url_type + "?name=" + os.path.basename(path))
# Create file stream and array.
file_reader = open(path, 'rb')
web_request = bytearray()
# Get size of file by reading the bytes.
file_size = 0
while True:
file_bytes = file_reader.read(UploadFile().MAX_CHUNK_SIZE)
file_size += len(file_bytes)
if len(file_bytes) == 0:
break
file_reader.close()
content_length = file_size
if use_mte:
# If we are using the MTE, adjust the content length.
content_length += encoder.encrypt_finish_bytes()
remaining_bytes = file_size
number_of_bytes_read = 0
if use_mte:
# Restore Encoder from state.
status = encoder.restore_state_b64(encoder_state)
if status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_STATE_RETRIEVAL
upload_response.message = "Failed to restore MTE Encoder engine. Status: {0} / {1}".format(
encoder.get_status_name(status), encoder.get_status_description(status))
return upload_response
# Start the chunking session.
status = encoder.start_encrypt()
if status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_ENCODE_EXCEPTION
upload_response.message = "Failed to start encode chunk. Status: {0} / {1}".format(
encoder.get_status_name(status), encoder.get_status_description(status))
return upload_response
file_reader = open(path, 'rb')
# Read all bytes from the file.
while number_of_bytes_read < file_size:
file_bytes = file_reader.read(UploadFile().MAX_CHUNK_SIZE)
if use_mte:
# Encode the data in place - encoded data is put back in place in the buffer.
chunk_status = encoder.encrypt_chunk(file_bytes)
if chunk_status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_ENCODE_EXCEPTION
upload_response.message = "Failed to encode chunk. Status: {0} / {1}".format(
encoder.get_status_name(chunk_status), encoder.get_status_description(chunk_status))
return upload_response
# Write the data to the array.
if len(file_bytes) > 0:
web_request.extend(file_bytes)
number_of_bytes_read += len(file_bytes)
remaining_bytes -= len(file_bytes)
if use_mte:
# Finish the chunking session.
final_encoded_chunk, finish_status = encoder.finish_encrypt()
if finish_status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_ENCODE_EXCEPTION
upload_response.message = "Failed to finish encode chunk. Status: {0} / {1}".format(
encoder.get_status_name(finish_status), encoder.get_status_description(finish_status))
return upload_response
# Append the final data to the array.
if len(final_encoded_chunk) > 0:
web_request.extend(final_encoded_chunk)
# Save the Encoder state.
upload_response.data.encoder_state = encoder.save_state_b64()
# Get the response.
headers = {
"Content-Type": "application/json",
"accept": "*/*",
"Content-Length": str(content_length),
Constants().CLIENT_ID_HEADER: client_id
}
api_handshake_response = requests.post(
url=file_url, data=web_request, headers=headers)
text_response = api_handshake_response.json()
# Get the return text.
if text_response['Success'] == False:
# Check if we need to re-handshake.
if text_response['ResultCode'] == Constants().RC_MTE_STATE_NOT_FOUND:
# The server does not thave this client's state - we should re-handshake.
handshake_response = self.handshake_with_server(client_id)
# Return response, if successful give message to try again.
upload_response.data = handshake_response
upload_response.message = "Server lost MTE state, client needed to handshake again, handshake successful, please try again."
return upload_response
if use_mte:
# Restore Decoder.
decoder = MteMkeDec.fromdefault()
status = decoder.restore_state_b64(decoder_state)
if status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_STATE_RETRIEVAL
upload_response.message = "Failed to restore MTE Decoder engine. Status: {0} / {1}".format(
decoder.get_status_name(status), decoder.get_status_description(status))
return upload_response
# Start the chunking session.
status = decoder.start_decrypt()
if status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_DECODE_EXCEPTION
upload_response.message = "Failed to start decode chunk. Status: {0} / {1}".format(
decoder.get_status_name(status), decoder.get_status_description(status))
return upload_response
# Decode the data.
decoded_chunk = decoder.decrypt_chunk(
base64.b64decode(text_response['Data']))
clear_bytes, final_status = decoder.finish_decrypt()
if clear_bytes == NULL:
clear_bytes = bytearray(1)
if final_status != MteStatus.mte_status_success:
upload_response.success = False
upload_response.result_code = Constants().RC_MTE_DECODE_EXCEPTION
upload_response.message = "Failed to finish decode chunk. Status: {0} / {1}".format(
decoder.get_status_name(final_status), decoder.get_status_description(final_status))
return upload_response
# Set decoded message.
decoded_message = decoded_chunk + clear_bytes
# Return the server response.
upload_response.data.server_response = decoded_message
# Save the Decoder state.
upload_response.data.decoder_state = decoder.save_state_b64()
else:
# Return the server response.
upload_response.data.server_response = base64.b64decode(
text_response['Data'])
# Update the jwt/access_token
upload_response.access_token = text_response['access_token']
upload_response.success = True
return upload_response
//----------------------------------------------------------------------
// The file structure for the Go project should include the following:
//----------------------------------------------------------------------
// - mte
// fileUpload.go
// go.mod
// go.sum
//----------------------------------------------------------------------
// Below only pertinent MTE file code is included
// Links are included at the bottom of this page to download entire project
//-------------------------------------------------------------------------
fileUpload.go
package main
import (
"bufio"
"bytes"
"eclypsesEcdh"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"strconv"
"strings"
"fileUpload/mte"
"github.com/google/uuid"
)
var encoderState string
var decoderState string
const (
//--------------------
// Content type const
jsonContent = "application/json"
chunkSize = 1024
clientIdHeader = "x-client-id"
//---------------------------
// Connection and Route urls
restAPIName = "https://dev-echo.eclypses.com" // Public Echo API
// restAPIName = "http://localhost:52603" // Local MteDemo API
// restAPIName = "http://localhost:5000" // Local Generic API
handshakeRoute = "/api/handshake"
fileUploadNoMteRoute = "/FileUpload/nomte?name="
fileUploadMteRoute = "/FileUpload/mte?name="
//-------------------------
// MTE license information
companyName = ""
companyLicense = ""
useMte = true
//--------------------------
// Error return exit codes
errorPerformingHandshake = 101
errorMarshalJson = 102
errorHttpPost = 103
errorReadingResponse = 104
errorHttpGet = 105
errorInvalidConnectionMethod = 106
errorFromServer = 107
errorDecodingPK = 108
errorCreatingPK = 109
errorCreatingSS = 110
errorCreatingEncoder = 111
errorCreatingDecoder = 112
errorBase64Decoding = 113
errorDecodingData = 114
endProgram = 120
)
type HandshakeModel struct {
TimeStamp string
ConversationIdentifier string
ClientEncoderPublicKey string
ClientDecoderPublicKey string
}
type ResponseModel[T any] struct {
Message string
Success bool
ResultCode string
ExceptionUid string
access_token string
token_expires_in string
Data T
}
/**
* Main function kicks off the ECDH Handshake
*/
func main() {
//----------------------------------------------------
// Defer the exit so all other defer calls are called
retcode := 0
defer func() { os.Exit(retcode) }()
//--------------------
// Initialize client
clientId := uuid.New()
//------------------------
// Call Handshake Method
retcode, err := PerformHandshakeWithServer(clientId.String())
if err != nil {
fmt.Println("Error: " + err.Error() + " Code: " + strconv.Itoa(retcode))
return
}
//------------------------------
// Loop till user chooses to end
for {
//------------------------------------
// Prompting message for file to copy
fmt.Print("Please enter path of file to upload\n")
reader := bufio.NewReader(os.Stdin)
fPath, _ := reader.ReadString('\n')
//---------------------------
// Take off carriage return
fPath = strings.Replace(fPath, "\n", "", -1)
fPath = strings.Replace(fPath, "\r", "", -1)
//--------------------------------
// Check to make sure file exists
_, err := os.Stat(fPath)
if err != nil {
fmt.Printf("Path does not exist! %s", err)
return
}
//-----------------------
// Create MTE from state
encoder := mte.NewMkeEncDef()
defer encoder.Destroy()
if useMte {
encoderStatus := encoder.RestoreStateB64(encoderState)
if encoderStatus != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Encoder restore error (%v): %v\n",
mte.GetStatusName(encoderStatus), mte.GetStatusDescription(encoderStatus))
retcode = int(encoderStatus)
return
}
//---------------------
// Initialize Chunking
encoderStatus = encoder.StartEncrypt()
if encoderStatus != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "MTE Encoder StartEncrypt error (%v): %v\n",
mte.GetStatusName(encoderStatus), mte.GetStatusDescription(encoderStatus))
retcode = int(encoderStatus)
return
}
}
//----------------------------
// Open file and retrieve info
file, _ := os.Open(fPath)
fi, _ := file.Stat()
defer file.Close()
//----------
// Set URI
var route string
if useMte {
route = fileUploadMteRoute
} else {
route = fileUploadNoMteRoute
}
uri := restAPIName + route + fi.Name()
//-------------------------
// Calculate content length
totalSize := fi.Size()
//------------------------------------------------------------
// If we are using the MTE add additional length to totalSize
if useMte {
totalSize += int64(encoder.EncryptFinishBytes())
}
//-------------------------
// Use pipe to pass request
rd, wr := io.Pipe()
defer rd.Close()
go func() {
defer wr.Close()
//-------------
// Write file
buf := make([]byte, chunkSize)
for {
n, err := file.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
if useMte {
//-----------------------------
// End of the file reached
// Finish the chunking session
//-----------------------------
finishEncode, status := encoder.FinishEncrypt()
if status != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Encode finish error (%v): %v\n",
mte.GetStatusName(status), mte.GetStatusDescription(status))
}
//-------------------------------------------------
// If there are bytes to write, write them to file
//-------------------------------------------------
if finishEncode != nil {
if _, err := wr.Write(finishEncode); err != nil {
fmt.Printf("Error trying to write to file %s, err: %s", fi.Name(), err)
}
}
//--------------------
// Save state Encoder
encoderState = encoder.SaveStateB64()
}
}
break
}
//---------------------------------------
// If we are using MTE encrypt the chunk
if useMte {
if n < chunkSize {
buf = buf[:n]
}
//-----------------------------------------------------------
// Encrypt the chunk
encoderStatus := encoder.EncryptChunk(buf)
if encoderStatus != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Encode error (%v): %v\n",
mte.GetStatusName(encoderStatus), mte.GetStatusDescription(encoderStatus))
break
}
}
_, _ = wr.Write(buf[:n])
}
}()
//--------------------------
// Construct request with rd
req, _ := http.NewRequest("POST", uri, rd)
req.Header.Set(clientIdHeader, clientId.String())
req.ContentLength = totalSize
//-----------------
// Process request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
retcode = errorReadingResponse
return
} else {
body := &bytes.Buffer{}
_, _ = body.ReadFrom(resp.Body)
defer resp.Body.Close()
//------------------------------------------
// Marshal json response to Response object
hrBytes, _ := ioutil.ReadAll(body)
var serverResponse ResponseModel[string]
json.Unmarshal(hrBytes, &serverResponse)
if !serverResponse.Success {
fmt.Println("Error back from server: " + serverResponse.Message + " Code: " + strconv.Itoa(errorFromServer))
retcode = errorFromServer
return
}
var decodedText []byte
//-----------------------------------------------------
// Decode the response message if we are using the MTE
decoder := mte.NewMkeDecDef()
defer decoder.Destroy()
if useMte {
//--------------------------------------------
// Base64 Decode server response to []byte
encodedDatab64 := make([]byte, base64.StdEncoding.DecodedLen(len(serverResponse.Data)))
n, err := base64.StdEncoding.Decode(encodedDatab64, []byte(serverResponse.Data))
if err != nil {
fmt.Println("Error base64 decode encoded data: " + err.Error() + " Code: " + strconv.Itoa(errorDecodingData))
retcode = errorDecodingData
return
}
encodedData := encodedDatab64[:n]
//----------------------------
// Restore the decoder state
decoderStatus := decoder.RestoreStateB64(decoderState)
if decoderStatus != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Decoder restore error (%v): %v\n",
mte.GetStatusName(decoderStatus), mte.GetStatusDescription(decoderStatus))
retcode = int(decoderStatus)
return
}
//---------------------
// Initialize Chunking
decoderStatus = decoder.StartDecrypt()
if decoderStatus != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "MTE Decoder startDecrypt error (%v): %v\n",
mte.GetStatusName(decoderStatus), mte.GetStatusDescription(decoderStatus))
retcode = int(decoderStatus)
return
}
//-----------------------------------------------------------
// The response is going to be short don't need to loop
//-----------------------------------------------------------
// Decrypt the chunk
decodedData := decoder.DecryptChunk(encodedData)
if decodedData == nil {
fmt.Fprintf(os.Stderr, "Decode error.\n")
retcode = errorDecodingData
return
}
//-----------------------
// Finish Decode chunk
finishDecodeChunk, decoderStatus := decoder.FinishDecrypt()
if decoderStatus != mte.Status_mte_status_success {
//--------------------------------------------------------
// Decode finish decrypt unsuccessful and cannot continue
fmt.Fprintf(os.Stderr, "MTE Decoder finishDecrypt error (%v): %v\n",
mte.GetStatusName(decoderStatus), mte.GetStatusDescription(decoderStatus))
retcode = int(decoderStatus)
return
}
//--------------------------------------------------
// Check if there are additional bytes; if so, append
if finishDecodeChunk != nil {
decodedText = append(decodedData[:], finishDecodeChunk[:]...)
} else {
decodedText = decodedData
}
//-------------------
// Save decode state
decoderState = decoder.SaveStateB64()
} else {
//-------------------------------
// Base64 Decode response string
decodedText, err = base64.StdEncoding.DecodeString(string(serverResponse.Data))
if err != nil {
fmt.Println("Error base64 decoding string")
retcode = errorBase64Decoding
return
}
}
//-------------------------------
// Print out response from server
fmt.Println("Response from server: " + string(decodedText))
}
//------------------------------------------------
// Prompt user if they want to upload another file
fmt.Print("\nWould you like to upload another file (y/n)?\n")
qReader := bufio.NewReader(os.Stdin)
uploadAgain, _ := qReader.ReadString('\n')
//---------------------------
// Take off carriage return
uploadAgain = strings.Replace(uploadAgain, "\n", "", -1)
uploadAgain = strings.Replace(uploadAgain, "\r", "", -1)
//----------------------
// Check user response
if strings.ToLower(uploadAgain) == "n" {
fmt.Println("Program stopped.")
retcode = endProgram
return
}
}
}
/**
* Performs Handshake with Server
* Creates the ECDH public keys and sends them to server
* When the client receives it back generate the shared secret
* then creates the Encoder and Decoder and saves the states
*
* clientId: clientId string
*
* Returns HandshakeResponse: encoderSharedSecret, decoderSharedSecret
*
*/
func PerformHandshakeWithServer(clientId string) (out int, err error) {
fmt.Println("Performing handshake for client: " + clientId)
//--------------------------------------------
// Set default return and response parameters
var handshakeModel HandshakeModel
handshakeModel.ConversationIdentifier = clientId
//----------------------------------------------
// Create eclypses ECDH for Encoder and Decoder
encoderEcdh := eclypsesEcdh.New()
decoderEcdh := eclypsesEcdh.New()
//----------------------------
// Get the Encoder public key
clientEncoderPKBytes, err := encoderEcdh.GetPublicKey()
if err != nil {
fmt.Println("Error creating Encoder public key: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingPK))
return errorCreatingPK, err
}
//----------------------------
// Get the Decoder public key
clientDecoderPKBytes, err := decoderEcdh.GetPublicKey()
if err != nil {
fmt.Println("Error creating Decoder public key: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingPK))
return errorCreatingPK, err
}
//-----------------------------------------
// Base64 encode keys so we can send them
handshakeModel.ClientEncoderPublicKey = base64.StdEncoding.EncodeToString(clientEncoderPKBytes)
handshakeModel.ClientDecoderPublicKey = base64.StdEncoding.EncodeToString(clientDecoderPKBytes)
//----------------------------------
// Json encode our handshake model
handshakeString, err := json.Marshal(handshakeModel)
if err != nil {
fmt.Println("Error marshalling handshakeModel: " + err.Error() + " Code: " + strconv.Itoa(errorMarshalJson))
return errorMarshalJson, err
}
//----------------------------------
// Make Http and get return string
hsModelString, errorcode, err := MakeHttpCall(restAPIName+handshakeRoute, "POST", clientId, jsonContent, string(handshakeString))
if err != nil {
fmt.Println("Error making Http call: " + err.Error() + " Code: " + strconv.Itoa(errorcode))
return errorcode, err
}
//-----------------------------
// Marshal json back to class
hrBytes := []byte(hsModelString)
var serverResponse ResponseModel[HandshakeModel]
json.Unmarshal(hrBytes, &serverResponse)
if !serverResponse.Success {
fmt.Println("Error back from server: " + serverResponse.Message + " Code: " + strconv.Itoa(errorFromServer))
return errorFromServer, errors.New(serverResponse.Message)
}
//--------------------------------------------
// Base64 Decode Encoder public key to []byte
partnerEncoderPublicKeyb64 := make([]byte, base64.StdEncoding.DecodedLen(len(serverResponse.Data.ClientEncoderPublicKey)))
n, err := base64.StdEncoding.Decode(partnerEncoderPublicKeyb64, []byte(serverResponse.Data.ClientEncoderPublicKey))
if err != nil {
fmt.Println("Error base64 decode encoderPK: " + err.Error() + " Code: " + strconv.Itoa(errorDecodingPK))
return errorDecodingPK, err
}
partnerEncoderPublicKeyBytes := partnerEncoderPublicKeyb64[:n]
//--------------------------------------------
// Base64 Decode Decoder public key to []byte
partnerDecoderPublicKeyb64 := make([]byte, base64.StdEncoding.DecodedLen(len(serverResponse.Data.ClientDecoderPublicKey)))
n, err = base64.StdEncoding.Decode(partnerDecoderPublicKeyb64, []byte(serverResponse.Data.ClientDecoderPublicKey))
if err != nil {
fmt.Println("Error base64 decode decoderPK: " + err.Error() + " Code: " + strconv.Itoa(errorDecodingPK))
return errorDecodingPK, err
}
partnerDecoderPublicKeyBytes := partnerDecoderPublicKeyb64[:n]
//-------------------------------
// Create Encoder shared secret
enSSBytes, err := encoderEcdh.CreateSharedSecret(partnerEncoderPublicKeyBytes, nil)
if err != nil {
fmt.Println("Error creating Encoder shared secret: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingSS))
return errorCreatingSS, err
}
//-----------------------------
// Create Decoder shared secret
deSSBytes, err := decoderEcdh.CreateSharedSecret(partnerDecoderPublicKeyBytes, nil)
if err != nil {
fmt.Println("Error creating Decoder shared secret: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingSS))
return errorCreatingSS, err
}
//-------------------------
// Clear out container
encoderEcdh.ClearContainer()
decoderEcdh.ClearContainer()
//------------------------------------
// Check version and output to screen
//------------------------------------
mteVersion := mte.GetVersion()
fmt.Printf("Using Mte Version %s\n", mteVersion)
//--------------------------------
// Check license -- use constants
// If no license can be blank
//--------------------------------
if !mte.InitLicense(companyName, companyLicense) {
fmt.Println("There was an error attempting to initialize the MTE License.")
return
}
//---------------------------------
// Create MTE Encoder and Decoder
retcode, err := CreateMteEncoder(serverResponse.Data.TimeStamp, clientId, enSSBytes)
if err != nil {
fmt.Println("Error creating Encoder: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingEncoder))
return retcode, err
}
retcode, err = CreateMteDecoder(serverResponse.Data.TimeStamp, clientId, deSSBytes)
if err != nil {
fmt.Println("Error creating Decoder: " + err.Error() + " Code: " + strconv.Itoa(errorCreatingDecoder))
return retcode, err
}
return 0, nil
}
func CreateMteEncoder(timestamp string, clientId string, encoderEntropy []byte) (out int, err error) {
encoder := mte.NewMkeEncDef()
defer encoder.Destroy()
//----------------------------
// Parse nonce from timestamp
nonce, err := strconv.ParseUint(timestamp, 10, 64)
if err != nil {
panic(err)
}
//--------------------
// Initialize Encoder
//--------------------
encoder.SetEntropy(encoderEntropy)
encoder.SetNonceInt(nonce)
status := encoder.InstantiateStr(clientId)
if status != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Encoder instantiate error (%v): %v\n",
mte.GetStatusName(status), mte.GetStatusDescription(status))
return int(status), errors.New("encoder instantiate error (" + mte.GetStatusName(status) + "):" + mte.GetStatusDescription(status))
}
encoderState = encoder.SaveStateB64()
return 0, nil
}
func CreateMteDecoder(timestamp string, clientId string, decoderEntropy []byte) (out int, err error) {
decoder := mte.NewMkeEncDef()
defer decoder.Destroy()
//----------------------------
// Parse nonce from timestamp
nonce, err := strconv.ParseUint(timestamp, 10, 64)
if err != nil {
panic(err)
}
//--------------------
// Initialize Decoder
//--------------------
decoder.SetEntropy(decoderEntropy)
decoder.SetNonceInt(nonce)
status := decoder.InstantiateStr(clientId)
if status != mte.Status_mte_status_success {
fmt.Fprintf(os.Stderr, "Decoder instantiate error (%v): %v\n",
mte.GetStatusName(status), mte.GetStatusDescription(status))
return int(status), errors.New("decoder instantiate error (" + mte.GetStatusName(status) + "):" + mte.GetStatusDescription(status))
}
decoderState = decoder.SaveStateB64()
return 0, nil
}
/**
* Makes Http Call
*
* route: Route to make the Http call
* connectionMethod: POST OR GET
* clientId: clientId string
* contentType: string with content type description
* payload: post payload string
*
* Returns a json string of what server sends back
*
*/
func MakeHttpCall(route string,
connectionMethod string,
clientId string,
contentType string,
payload string) (out string, retcode int, err error) {
//--------------------
// Set return string
var returnString string
//------------------------------------
// If this is a POST request do this
if strings.ToUpper(connectionMethod) == "POST" {
responseBody := bytes.NewBuffer([]byte(payload))
resp, err := http.Post(route, contentType, responseBody)
if err != nil {
fmt.Println("An Error Occured %v", err)
return "", errorHttpPost, err
}
defer resp.Body.Close()
//-----------------------
// Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return "", errorReadingResponse, err
}
//--------------------------------
// Convert the body to type string
returnString = string(body)
//------------------------------------
// If this is a GET request do this
} else if connectionMethod == "GET" {
resp, err := http.Get(route)
if err != nil {
fmt.Println(err.Error())
return "", errorHttpGet, err
}
//------------------------
// Read the response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err.Error())
return "", errorReadingResponse, err
}
//--------------------------------
// Convert the body to type string
returnString = string(body)
} else {
fmt.Println("Invalid connection request")
return "", errorInvalidConnectionMethod, errors.New("invalid connection request")
}
return returnString, 0, nil
}
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.
- 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
}
}
Full File Upload Sample Project
Eclypses has developed full sample projects demonstrating a File Upload using the MTE MKE add-on.
Console Application
C# Web API