Skip to main content

MTE Multiple Client Samples

Purpose

Samples expand on our "Examples" by providing longer snippets of code demonstrating the entire processes. The objective of an MTE Multiple Client "Samples" is to provide full samples of code of how to use the MTE with multiple clients.

The MTE requires each endpoint to be paired individually. For an endpoint to manage multiple MTE endpoints each endpoint must be tracked and be identifiable. When there are multiple MTE clients an easy way to manage all the clients is to save and restore the MTE state of each endpoint. This sample shows how to save and restore the MTE state in order to manage multiple MTE clients.

The sample below uses a console application that will prompt the user for the number of clients to create. Then it will do the following for each client.

  • Handshake with server to pair MTE
  • Create MTE Encoder
  • Encrypt and save MTE encoder state
  • Create MTE Decoder
  • Encrypt and save MTE decoder state
  • Sends a random number of messages, performing the following steps:
    • Decrypts MTE encoder state
    • Restores MTE encoder for client
    • Encodes outgoing message with MTE
    • Encrypts and saves updated MTE encoder state
    • Decrypts MTE decoder state
    • Restores MTE decoder for client
    • Decodes incoming message using MTE
    • Encrypts and saves updated MTE decoder state
  • User is then prompted to either send additional messages or end.

The code samples for each section show the client side using a console application, the operations on the server side are almost identical. Each section breaks out portions of the code, at the bottom of this page the full program files are also available for the client as well as the server.

Initial Pairing Handshake

Our example pairs the MTE client using an initial handshake that uses the Diffie-Hellman key exchange to determine the entropy, the conversationID for the personalization string and the server creates and sets the nonce and sends it back to the client.

/// <summary>
/// Handshakes the with server.
/// </summary>
/// <param name="clientId">The client identifier.</param>
/// <param name="clients">The client dictionary</param>
/// <param name="currentConversation">The current conversation GUID.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
private static bool HandshakeWithServer(int clientId,
Dictionary<int, string> clients,
string currentConversation = null)
{
try
{
Console.WriteLine($"Performing Handshake for Client {clientId}");

//--------------------------------
// create clientId for this client
//--------------------------------
HandshakeModel handshake = new HandshakeModel {
ConversationIdentifier = Guid.NewGuid().ToString() };

//-------------------------------------------------------------
// If current conversation guid is passed in, update identifier
//-------------------------------------------------------------
if (!string.IsNullOrWhiteSpace(currentConversation))
{
handshake.ConversationIdentifier = currentConversation;
}

//-------------------------------------------------------------
// Add client to dictionary list if this is a new conversation
//-------------------------------------------------------------
if (!clients.ContainsKey(clientId))
{
clients.Add(clientId, handshake.ConversationIdentifier);
}

//-------------------------------------------
// 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> response =
JsonSerializer.Deserialize<ResponseModel<HandshakeModel>>(handshakeResponse, _jsonOptions);

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

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

//----------------------------------------------------------
// Create and store MTE Encoder and Decoder for this Client
//----------------------------------------------------------
ResponseModel mteResponse = CreateMteStates(response.Data.ConversationIdentifier,
encoderSharedSecretModel.SharedSecret,
decoderSharedSecretModel.SharedSecret,
Convert.ToUInt64(response.Data.Timestamp));

//----------------------------------------------------------
// Clear container to ensure key is different for each client
//----------------------------------------------------------
encoderEcdh.ClearContainer();
decoderEcdh.ClearContainer();

//-----------------------------------------
// If there was an error break out of loop
//-----------------------------------------
if (!mteResponse.Success)
{
Console.WriteLine($"Error creating mte states for Client {clientId}: {response.Message}");
return false;
}

return true;
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}

Create MTE states

During the initial handshake process the MTE Encoder and MTE Decoder are created and the states are encrypted and stored to our memory cache. Both the server and the client use the same code. Below is a code sample of this process:

/// <summary>
/// Creates the mte states.
/// </summary>
/// <param name="personal">The personal.</param>
/// <param name="entropy">The entropy.</param>
/// <param name="nonce">The nonce.</param>
/// <returns>ResponseModel.</returns>
private static ResponseModel CreateMteStates(string personal,
byte[] encoderEntropy,
byte[] decoderEntropy,
ulong nonce)
{
ResponseModel response = new ResponseModel();
try
{
//--------------------
// Create MTE Encoder
//--------------------
MteEnc encoder = new MteEnc();
encoder.SetEntropy(encoderEntropy);
encoder.SetNonce(nonce);
MteStatus status = encoder.Instantiate(personal);
if (status != MteStatus.mte_status_success)
{
Console.WriteLine($"Error creating encoder: Status: " +
"{encoder.GetStatusName(status)} / {encoder.GetStatusDescription(status)}");
response.Message =
$"Error creating encoder: Status: " +
"{encoder.GetStatusName(status)} / {encoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.Success = false;
return response;
}

//------------------------
// Save and encrypt state
//------------------------
var encoderState = encoder.SaveStateB64();
var encryptedEncState = _enc.Encrypt(encoderState, personal, _encIV);
Constants.MteClientState.Store($"{Constants.EncoderPrefix}{personal}",
encryptedEncState,
TimeSpan.FromMinutes(Constants.ExpireMinutes));

//--------------------
// Create MTE Decoder
//--------------------
MteDec decoder = new MteDec();
decoder.SetEntropy(decoderEntropy);
decoder.SetNonce(nonce);
status = decoder.Instantiate(personal);
if (status != MteStatus.mte_status_success)
{
Console.WriteLine($"Error creating decoder: Status: " +
"{decoder.GetStatusName(status)} / {decoder.GetStatusDescription(status)}");
response.Message =
$"Error creating decoder: Status: " +
"{decoder.GetStatusName(status)} / {decoder.GetStatusDescription(status)}";
response.ResultCode = Constants.RC_MTE_DECODE_EXCEPTION;
response.Success = false;
return response;
}

//------------------------
// Save and encrypt state
//------------------------
var decodeState = decoder.SaveStateB64();
var encryptedDecState = _enc.Encrypt(decodeState, personal, _encIV);

Constants.MteClientState.Store($"{Constants.DecoderPrefix}{personal}",
encryptedDecState,
TimeSpan.FromMinutes(Constants.ExpireMinutes));
response.Success = true;
response.ResultCode = Constants.RC_SUCCESS;
response.Message = Constants.STR_SUCCESS;
}
catch (Exception ex)
{
response.Message = $"Exception creating MTE state. Ex: {ex.Message}";
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.Success = false;
}

return response;
}

Restoring the MTE state Client side

The last step is for each client to send messages to the server encoded with their own instance of the MTE. The next example shows how to take the saved MTE state and restore it and then use the MTE to encode or decode a message.

/// <summary>
/// Contacts the server.
/// </summary>
/// <param name="rnd">The random.</param>
/// <param name="currentConversation">The current conversation.</param>
/// <param name="clientNum">The i.</param>
/// <param name="clients">The clients.</param>
/// <exception cref="System.ApplicationException">Error restoring the encoder mte state for
/// Client {i}: {encoder.GetStatusDescription(encoderStatus)}</exception>
/// <exception cref="System.ApplicationException">Error restoring the decoder mte state for
/// Client {i}: {decoder.GetStatusDescription(decoderStatus)}</exception>
/// <exception cref="System.ApplicationException"></exception>
private static async Task ContactServer(Random rnd,
string currentConversation,
int clientNum,
Dictionary<int, string> clients)
{
try
{
//-------------------------------------------------
// Randomly select number of trips between 1 and max number of trips
//-------------------------------------------------
int numberTrips = rnd.Next(1, _maxNumberOfTrips);

//---------------------------------------
// Send message selected number of trips
//---------------------------------------
for (int t = 0; t < numberTrips; t++)
{
//-------------------------------------
// Get the current client encoder state
//-------------------------------------
string encoderState =
Constants.MteClientState.Get($"{Constants.EncoderPrefix}{currentConversation}");
string decryptedEncState =
_enc.Decrypt(encoderState, currentConversation, _encIV);

//-------------------------------------
// Restore the Encoder ensure it works
//-------------------------------------
MteEnc encoder = new MteEnc();
MteStatus encoderStatus = encoder.RestoreStateB64(decryptedEncState);
if (encoderStatus != MteStatus.mte_status_success)
{
Console.WriteLine($"Error restoring the encoder mte state for Client " +
"{clientNum}: {encoder.GetStatusDescription(encoderStatus)}");
throw new ApplicationException($"Error restoring the encoder mte state for Client " +
"{clientNum}: {encoder.GetStatusDescription(encoderStatus)}");
}

//-------------------------------------
// Get the current client decoder state
//-------------------------------------
string decoderState =
Constants.MteClientState.Get($"{Constants.DecoderPrefix}{currentConversation}");
string decryptedDecState =
_enc.Decrypt(decoderState, currentConversation, _encIV);

//-------------------------------------
// Restore the Decoder ensure it works
//-------------------------------------
MteDec decoder = new MteDec();
MteStatus decoderStatus = decoder.RestoreStateB64(decryptedDecState);
if (decoderStatus != MteStatus.mte_status_success)
{
Console.WriteLine($"Error restoring the decoder mte state for Client " +
"{clientNum}: {decoder.GetStatusDescription(decoderStatus)}");
throw new ApplicationException($"Error restoring the decoder mte state for Client " +
"{clientNum}: {decoder.GetStatusDescription(decoderStatus)}");
}

//-------------------------
// Encode message to send
//-------------------------
string message = $"Hello from client {clientNum} for the {t + 1} time.";
string encodedPayload = encoder.EncodeB64(message);
Console.WriteLine($"Sending message '{message}' to multi client server.");

//-----------------------------------------------------------
// Send encoded message to server, putting clientId in header
//-----------------------------------------------------------
string multiClientResponse =
MakeHttpCall($"{_restAPIName}/api/multiclient",
HttpMethod.Post,
currentConversation,
_textContentType,
encodedPayload).Result;

//----------------------
// deserialize response
//----------------------
ResponseModel<string> serverResponse =
JsonSerializer.Deserialize<ResponseModel<string>>(multiClientResponse, _jsonOptions);
if (!serverResponse.Success)
{
if (serverResponse.ResultCode.Equals(Constants.RC_MTE_STATE_NOT_FOUND,
StringComparison.InvariantCultureIgnoreCase))
{
//-------------------------------------------------------------------------
// the server does not have this client's state - we should "re-handshake"
//-------------------------------------------------------------------------
bool handshakeIsSuccessful = HandshakeWithServer(clientNum, clients, currentConversation);
if (!handshakeIsSuccessful)
{
Console.WriteLine($"Error from server for client " +
"{clientNum}: {serverResponse.Message}");
throw new ApplicationException();
}
//-----------------------------------------------------------------
// break out of this loop so we can contact again after handshake
//-----------------------------------------------------------------
return;
}
}

//---------------------------------------------------
// If this was successful save the new encoder state
//---------------------------------------------------
encoderState = encoder.SaveStateB64();
var encryptedEncState = _enc.Encrypt(encoderState, currentConversation, _encIV);
Constants.MteClientState.Store($"{Constants.EncoderPrefix}{currentConversation}",
encryptedEncState,
TimeSpan.FromMinutes(Constants.ExpireMinutes));

//-----------------------------
// decode the incoming message
//-----------------------------
string decodedMessage = decoder.DecodeStrB64(serverResponse.Data, out decoderStatus);
if (decoderStatus != MteStatus.mte_status_success)
{
Console.WriteLine($"Error restoring the decoder mte state for Client " +
"{clientNum}: {decoder.GetStatusDescription(decoderStatus)}");
throw new ApplicationException($"Error restoring the decoder mte state for Client " +
"{clientNum}: {decoder.GetStatusDescription(decoderStatus)}");
}

//----------------------------------------
// If decode is successful save new state
//----------------------------------------
decoderState = decoder.SaveStateB64();
var encryptedDecState = _enc.Encrypt(decoderState, currentConversation, _encIV);
Constants.MteClientState.Store($"{Constants.DecoderPrefix}{currentConversation}",
encryptedDecState,
TimeSpan.FromMinutes(Constants.ExpireMinutes));

//-------------------------------------
// Output incoming message from server
//-------------------------------------
Console.WriteLine($"Received '{decodedMessage}' from multi-client server.\n\n");
// Sleep between each call a random amount of time
// between 10 and 100 milli-seconds
Thread.Sleep(rnd.Next(0, 100));
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
}

Full Client Side Sample

Here is the entire console side application code.

Full C# Sample

Full Java Sample

Full Python Sample

Full Go Sample

Full Server Side API

The server side API is only written in CSharp and not in all the other languages. All of the client side console applications can be successfully run against the Server Side API. The server includes other projects as well, below I will only include files that include the multiple client example.

Full C# WebAPI Sample