Switching between MTE Core and MKE Samples
Purpose
Samples expand on our "Examples" by providing longer snippets of code demonstrating the entire processes. The objective of the samples below is to demonstrate how to use the MTE state to switch between the MTE Core and MKE. Most applications have transmissions that are very short and very lengthy, in this case it would be beneficial to switch between the MTE core and MKE to minimize message length while optimizing the application's security.
The MTE software allows you to use the same MTE state for the MTE Core, FLEN and MKE as long as each is initialized using the same options. A single client can switch between these based on the size of the message or other environmental needs. Below is an example that uses the MTE Core to transmit the login information and then uses the MKE to upload a file.
IMPORTANT NOTE
Each side must use the same MTE "type" in a single transmission in order to decode and encode the messages, for example if the message is encoded using the MTE Core it must be decoded using the MTE Core.
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 is generated on the client and sent to the server and the server creates and sets the nonce and sends it back to the client as part of the handshaking process. The MTE is also created and the state is saved.
- CSharp
- Java
- Swift
- Python
- Go
/// <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($"{Constants.RestAPIName}/api/handshake",
HttpMethod.Post,
handshake.ConversationIdentifier,
Constants.JsonContentType,
JsonSerializer.Serialize(handshake, Constants.JsonOptions)).Result;
//---------------------------------------
// Deserialize the result from handshake
//---------------------------------------
ResponseModel<HandshakeModel> serverResponse =
JsonSerializer.Deserialize<ResponseModel<HandshakeModel>>
(handshakeResponse, Constants.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;
}
/**
* 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(Constants.RestAPIName + Constants.HandshakeRoute,
"POST",
handshake.ConversationIdentifier,
Constants.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: "
+ encoder.getStatusName(status) + " / "
+ encoder.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: "
+ encoder.getStatusName(status) + " / "
+ encoder.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;
return response;
}
return response;
}
// This Swift Elliptic-Curve Diffie-Hellman implementation uses the EcdhHelper shown below
// for both Encoder and Decoder.
let ecdh = try EcdhHelper(name: "\(pairType.rawValue) Entropy")
let publicKey = try ecdh.getPublicKey()
// The device public key is sent to the server where it is used to create a shared secret.
// The Server then returns its public key that the device uses to create the same shared secret
// In this sample the shared secret itself is used as the entropy
var tempEntropy: [UInt8]()!
try ecdh.createSharedSecret(remotePublicKeyStr: response.publicKey,
entropy: &tempEntropy)
EcdhHelper.swift
// This helper is used by both Encoder and Decoder
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."
}
}
}
def handshake_with_server(self, client_id: str) -> 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
/**
* 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 there is no license,
// These values 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
}
Using MTE Core to Login
Next we take the MTE state, decrypt it and restore it to an MTE Core instance. Using this instance we encode a serialized string of the login. Following that, save and encrypt the MTE state. We do the same with the decoder state to decode the login response if successful.
- CSharp
- Java
- Swift
- Python
- Go
/// <summary>
/// Login to the server.
/// </summary>
/// <param name="clientId">The clientId.</param>
/// <param name="encoderState">The current encoderState.</param>
/// <param name="decoderState">The current decoderState.</param>
public ResponseModel<LoginResposne> LoginToServer(string clientId,
string encoderState,
string decoderState)
{
ResponseModel<LoginResposne> response = new ResponseModel<LoginResposne>
{ Data = new LoginResposne
{ EncoderState = encoderState, DecoderState = decoderState }
};
try
{
//-----------------------------------
// Login first before uploading file
// Use regular MTE
//-----------------------------------
string loginRoute = "api/login";
LoginModel login = new LoginModel
{ Password = "P@ssw0rd!", UserName = "email@eclypses.com" };
//---------------------------------
// Serialize login model and encode
//---------------------------------
string serializedLogin = JsonSerializer.Serialize(login);
//----------------------------------
// Encode outgoing message with MTE
//----------------------------------
MteEnc enc = new MteEnc();
MteStatus encoderStatus = enc.RestoreStateB64(encoderState);
if (encoderStatus != MteStatus.mte_status_success)
{
response.Message = $"Failed to restore MTE Encoder engine. Status: "
+ "{enc.GetStatusName(encoderStatus)} / "
+ "{enc.GetStatusDescription(encoderStatus)}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
return response;
}
string encodeResult = enc.EncodeB64(serializedLogin);
//---------------------------
// Save updated Encoder State
//---------------------------
response.Data.EncoderState = enc.SaveStateB64();
//-------------------
// Perform Login
//-------------------
string loginResponse =
MakeHttpCall($"{Constants.RestAPIName}/api/login", HttpMethod.Post, clientId,
Constants.TextContentType, encodeResult).Result;
//---------------------------------------
// Deserialize the result from login
//---------------------------------------
ResponseModel<string> serverResponse =
JsonSerializer.Deserialize<ResponseModel<string>>
(loginResponse, Constants.JsonOptions);
//--------------
// If error end
//--------------
if (!serverResponse.Success)
{
response.Message = serverResponse.Message;
response.Success = serverResponse.Success;
response.ExceptionUid = serverResponse.ExceptionUid;
response.ResultCode = serverResponse.ResultCode;
return response;
}
//----------------------
// Set jwt/access_token
//----------------------
response.access_token = serverResponse.access_token;
//---------------------------------------------
// Decode the response and resave Decoder State
//---------------------------------------------
MteDec dec = new MteDec();
MteStatus decoderStatus = dec.RestoreStateB64(decoderState);
if (decoderStatus != MteStatus.mte_status_success)
{
response.Message = $"Failed to restore MTE Decoder engine. Status: "
+ "{dec.GetStatusName(decoderStatus)} / "
+ "{dec.GetStatusDescription(decoderStatus)}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
return response;
}
var decodedResult = dec.DecodeStrB64(serverResponse.Data);
Console.WriteLine($"Login response: {decodedResult}");
response.Data.DecoderState = dec.SaveStateB64();
response.Data.LoginMessage = decodedResult;
}
catch (Exception ex)
{
response.Message = $"Exception during login: {ex.Message}";
response.Success = false;
response.ExceptionUid = Guid.NewGuid().ToString();
response.ResultCode = Constants.RC_LOGIN_EXCEPTION;
}
return response;
}
/**
* Login using MTE Core
* @param clientId -- conversation/client Id
* @param handshake - Encoder and Decoder states
* @return
*/
private static ResponseModel<LoginResponse> LoginToServer(String clientId,
HandshakeResponse handshake) {
ResponseModel<LoginResponse> response = new ResponseModel<LoginResponse>();
response.Data = new LoginResponse(handshake.EncoderState, handshake.DecoderState);
try {
//---------------------------------------------------
// Set login -- demo only this should be more secure
//---------------------------------------------------
LoginModel login = new LoginModel("email@eclypses.com","P@ssw0rd!");
//-----------------------------------
// Serialize and Encode with Core MTE
//-----------------------------------
String serializedLogin = _gson.toJson(login);
MteEnc mteEnc = new MteEnc();
MteStatus encoderStatus = mteEnc.restoreStateB64(handshake.EncoderState);
if(encoderStatus != MteStatus.mte_status_success) {
//--------------------------------
// Return error if not successful
//--------------------------------
response.Message = "Failed to restore MTE Encoder engine. Status: " +
MteBase.getStatusName(encoderStatus) + " / " +
MteBase.getStatusDescription(encoderStatus);
response.Success = false;
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
response.ExceptionUid = UUID.randomUUID().toString();
return response;
}
//--------------------------------------------
// Encode Login with MTE Core and Save State
//--------------------------------------------
StrStatus encodedLogin = mteEnc.encodeB64(serializedLogin);
if(encodedLogin.status != MteStatus.mte_status_success) {
//--------------------------------
// Return error if not successful
//--------------------------------
response.Message = "Failed to encode login. Status: " +
MteBase.getStatusName(encodedLogin.status) + " / " +
MteBase.getStatusDescription(encodedLogin.status);
response.Success = false;
response.ResultCode = Constants.RC_MTE_ENCODE_EXCEPTION;
response.ExceptionUid = UUID.randomUUID().toString();
return response;
}
response.Data.EncoderState = mteEnc.saveStateB64();
//----------------------------
// Make login call to server
//----------------------------
String serverResponse = MakeHttpCall(Constants.RestAPIName + Constants.LoginRoute,
"POST",
clientId,Constants.TextContentType,
encodedLogin.str);
//---------------------------------------
// Deserialize the result from login
//---------------------------------------
Type loginResponseType = new TypeToken<ResponseModel<String>>() {
}.getType();
ResponseModel<String> loginResponse =
_gson.fromJson(serverResponse, loginResponseType);
//------------------------------
// If unsuccessful return error
//------------------------------
if(!loginResponse.Success) {
return loginResponse.ReturnDataWithResponseModel(loginResponse, response.Data);
}
//-----------------------------
// Set return JWT/access_token
//-----------------------------
response.access_token = loginResponse.access_token;
//----------------------------------------------
// Restore State to MTE Core
//----------------------------------------------
MteDec mteDec = new MteDec();
MteStatus decoderStatus = mteDec.restoreStateB64(handshake.DecoderState);
if(decoderStatus != MteStatus.mte_status_success) {
response.Message = "Failed to restore MTE Decoder engine. Status: " +
mteDec.getStatusName(decoderStatus) + " / " +
mteDec.getStatusDescription(decoderStatus);
response.Success = false;
response.ResultCode = Constants.RC_MTE_STATE_RETRIEVAL;
response.ExceptionUid = UUID.randomUUID().toString();
return response;
}
//---------------------------------------
// Decode message and Save Decoder State
//---------------------------------------
StrStatus decodedMessage = mteDec.decodeStrB64(loginResponse.Data);
if(decodedMessage.status != MteStatus.mte_status_success) {
response.Message = "Failed to decode login message. Status: " +
MteBase.getStatusName(decodedMessage.status) + " / " +
MteBase.getStatusDescription(decodedMessage.status);
response.Success = false;
response.ResultCode = Constants.RC_MTE_DECODE_EXCEPTION;
response.ExceptionUid = UUID.randomUUID().toString();
return response;
}
response.Data.DecoderState = mteDec.saveStateB64();
//-------------------------------
// Output login response message
//-------------------------------
System.out.println("Login response: " + decodedMessage.str);
response.Data.LoginMessage = decodedMessage.str;
}catch (Exception ex) {
ex.printStackTrace();
response.Message = "Exception during login: " + ex.getMessage();
response.Success = false;
response.ResultCode = Constants.RC_LOGIN_EXCEPTION;
return response;
}
return response;
}
import Foundation
let _jsonEncoder = getJSONEncoder()
let _jsonDecoder = getJSONDecoder()
print("****** Simple MTE Switching 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, MteTimestampCallback {
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()
encodeAndSend(plaintext: "These are our [login credentials] that we need to keep secret!")
}
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()
print("Successfully Instantiated Encoder with Elliptic-Curve Diffie-Hellman Handshake\n")
}
fileprivate func instantiateDecoder() throws {
pairType = .decoder
mteHelper.decoderPersonalizationString = UUID().uuidString.lowercased()
try mteHelper.createDecoder()
print("Successfully Instantiated Decoder with Elliptic-Curve Diffie-Hellman Handshake\n")
}
func encodeAndSend(plaintext: String) {
var encodedData: Data!
if plaintext != "" {
do {
// encode to a string
let encodedPayloadStr = try mteHelper.encode(message: plaintext)
// and convert back to Data
encodedData = Data(encodedPayloadStr.utf8)
} catch {
print("Unable to encode Data. Error: \(error.localizedDescription)")
}
let connectionModel = ConnectionModel(url: Settings.serverUrl,
method: Constants.POST,
route: "api/mte/send-data",
payload: encodedData,
contentType: "text/plain; charset=utf-8",
clientId: Settings.clientId,
mteVersion: MteBase.getVersion())
WebService.call(connectionModel: connectionModel, onCompletion: { callResult in
switch callResult {
case .failure(let code, let message):
print("Communication with Server failed. ErrorCode: \(code). ErrorMessage: \(message)")
case .success(let data):
do {
// In this example, first, we'll convert the response data to a String
let dataStr = String(decoding: data, as: UTF8.self)
// then decode it
let decodedStr = try self.mteHelper.decode(encoded: dataStr)
print("Response from Server: \n\(decodedStr)")
// Call to upload a file when call to 'login' has completed.
// Uncomment for full Sample
// self.uploadStream("MobyDickeBook.txt")
} catch {
print("Unable to decode Data from Server. Error: \(error.localizedDescription)")
}
}
})
}
}
//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
}
}
func timestampCallback() -> UInt64 {
// return the timestamp in milliseconds
return UInt64(Date().timeIntervalSince1970 * 1000.0)
}
}
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()
print("Using MTE Version \(mteVersion)\n")
// 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))"
}
}
def login_to_server(self, client_id: str, encoder_state: str, decoder_state:str) -> ResponseModel:
"""
Login to the server using MTE Core.
"""
response = ResponseModel()
response.data = LoginResponse()
response.data.encoder_state = encoder_state
response.data.decoder_state = decoder_state
# Login first before uploading file.
# Use regular MTE.
login_route = "/api/login"
login = LoginModel()
login.password = "P@ssw0rd!"
login.username = "email@eclypses.com"
# Serialize login model and encode.
serialize_login = json.dumps(login, default=vars)
# Encode outgoing message with MTE.
enc = MteEnc.fromdefault()
encoder_status = enc.restore_state_b64(encoder_state)
if encoder_status != MteStatus.mte_status_success:
response.message = "Failed to restore MTE Encoder engine. Status: {0}: {1}".format(enc.get_status_name(encoder_status), enc.get_status_description(encoder_status))
response.success = False
response.exception_uid = str(uuid.uuid4())
response.result_code = Constants().RC_MTE_STATE_RETRIEVAL
return response
encode_result, encoder_status = enc.encode_b64(serialize_login)
if encoder_status != MteStatus.mte_status_success:
response.message = "Failed to encode the login. Status: {0}: {1}".format(enc.get_status_name(encoder_status), enc.get_status_description(encoder_status))
response.success = False
response.exception_uid = str(uuid.uuid4())
response.result_code = Constants().RC_MTE_ENCODE_EXCEPTION
return response
# Save updated Encoder state.
response.data.encoder_state = enc.save_state_b64()
# Perform Login.
headers = {
"Content-Type": "text/plain",
"accept": "*/*",
"Content-Length": str(len(encode_result)),
Constants().CLIENT_ID_HEADER: client_id
}
login_url = os.path.join(Constants().REST_API_NAME + login_route)
login_response = requests.post(
url=login_url, data=encode_result, headers=headers)
# Deserialize the result from login.
server_response = login_response.json()
# If there is an error then end.
if server_response['Success'] == False:
response.message = server_response['Message']
response.success = server_response['Success']
response.exception_uid = server_response["ExceptionUid"]
response.result_code = server_response['ResultCode']
return response
# Set jwt/access_token
response.access_token = server_response['access_token']
# Decode the response and resave Decoder state.
dec = MteDec.fromdefault()
decoder_status = dec.restore_state_b64(decoder_state)
if decoder_status != MteStatus.mte_status_success:
response.message = "Failed to restore MTE Decoder engine. Status: {0}: {1}".format(dec.get_status_name(decoder_status), dec.get_status_description(decoder_status))
response.success = False
response.exception_uid = str(uuid.uuid4())
response.result_code = Constants().RC_MTE_STATE_RETRIEVAL
return response
decoded_result, decoder_status = dec.decode_str_b64(server_response['Data'])
if decoder_status != MteStatus.mte_status_success:
response.message = "Failed to decode message. Status: {0}: {1}".format(dec.get_status_name(decoder_status), dec.get_status_description(decoder_status))
response.success = False
response.exception_uid = str(uuid.uuid4())
response.result_code = Constants().RC_MTE_DECODE_EXCEPTION
return response
print ("Login response: {0}".format(decoded_result))
response.data.decoder_state = dec.save_state_b64()
response.data.login_message = decoded_result
return response
/**
* Login to API server
* Uses default username and password
* Uses MTE Core
*/
func LoginToServer(clientId string) (out int, err error) {
//-----------------
// Set login model
// This is a demonstration, username and password should not
// Be put in code as plain text
login := LoginModel{
Password: "P@ssw0rd!",
UserName: "email@eclypses.com",
}
//--------------------------
// Serialize the login model
serializedLogin, err := json.Marshal(login)
if err != nil {
fmt.Println(err.Error())
return
}
//---------------------
// Create the Encoder
encoder := mte.NewEncDef()
defer encoder.Destroy()
//------------------------
// Restore Encoder state
encoderStatus := encoder.RestoreStateB64(encoderState)
if encoderStatus != mte.Status_mte_status_success {
errorMessage := "Encoder restore error (" + mte.GetStatusName(encoderStatus)
+ ", " + mte.GetStatusDescription(encoderStatus) + ")"
fmt.Println(errorMessage)
return int(encoderStatus), errors.New(errorMessage)
}
//----------------------------------
// Encode the serialized Login Model
encodedLogin, encoderStatus := encoder.EncodeB64(serializedLogin)
if encoderStatus != mte.Status_mte_status_success {
errorMessage := "Encode error " + mte.GetStatusName(encoderStatus)
+ " , " + mte.GetStatusDescription(encoderStatus)
fmt.Println(errorMessage)
return
}
//----------------------------
// Save updated Encoder state
encoderState = encoder.SaveStateB64()
//-----------------
// Make Http call
loginResponse, retcode, err := MakeHttpCall(restAPIName+loginRoute,
"POST",
clientId,
textContent,
encodedLogin)
if err != nil {
fmt.Println(err.Error())
return retcode, err
}
//-----------------------------
// Marshal json back to class
hrBytes := []byte(loginResponse)
var serverResponse ResponseModel[string]
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)
}
//-----------------------------------------
// Set access_token for next communication
access_token = serverResponse.access_token
//---------------------
// Decode the response
decoder := mte.NewDecDef()
defer decoder.Destroy()
//---------------------------
// Restore the Decoder state
decoderStatus := decoder.RestoreStateB64(decoderState)
if mte.StatusIsError(decoderStatus) {
errorMessage := "Decoder restore error (" + mte.GetStatusName(decoderStatus)
+ ", " + mte.GetStatusDescription(decoderStatus) + ")"
fmt.Println(errorMessage)
return int(decoderStatus), errors.New(errorMessage)
}
//-----------------------
// Decode return message
decodedMessage, decoderStatus := decoder.DecodeStrB64(serverResponse.Data)
if mte.StatusIsError(decoderStatus) {
errorMessage := "Decode error " + mte.GetStatusName(decoderStatus)
+ " , " + mte.GetStatusDescription(decoderStatus)
fmt.Println(errorMessage)
return int(decoderStatus), errors.New(errorMessage)
}
fmt.Println("Login Response: " + decodedMessage)
decoderState = decoder.SaveStateB64()
return 0, nil
}
Using MKE To Upload File to Server
Finally we take the same MTE state, decrypt it and restore it to an MTE MKE instance. Using this instance we chunk encode a file to upload to the server. Following that, we save and encrypt the MTE state once again.
- CSharp
- Java
- Swift
- Python
- Go
#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>
/// <param name="authHeader">The JWT auth header.</param>
/// <returns>System.Int32.</returns>
public ResponseModel<UploadResponse> Send(string path,
bool useMte,
string encoderState,
string decoderState,
string clientid,
string authHeader)
{
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($"{Constants.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);
}
if (!string.IsNullOrWhiteSpace(authHeader))
{
if (authHeader.StartsWith("Bearer"))
authHeader = authHeader.Substring("Bearer ".Length);
_webRequest.Headers.Add("Authorization", "Bearer " + authHeader);
}
_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;
uploadResponse.access_token = handshakeRessponse.access_token;
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);
}
//-----------------------------
// update the jwt/access_token
//-----------------------------
uploadResponse.access_token = textResponse.access_token;
}
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
//---------------------------------------------------
// Login and Handshake previous to this
//---------------------------------------------------
// Exerpt from file upload file
// See full FileUploader.java file for entire code
//---------------------------------------------------
//---------
// Set JWT
//---------
_jwt = loginResponse.access_token;
//---------------------------------------
// 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 = Constants.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);
//----------------------------------
// Add Authentication if jwt is set
//----------------------------------
if(_jwt!=null && _jwt !="") {
if(_jwt.startsWith("Bearer")) {
_jwt = _jwt.substring(0, "Bearer ".length());
}
connection.setRequestProperty(Constants.AuthHeader, "Bearer " + _jwt);
}
//---------------------------
// 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);
}
//----------------------
// finalize mte session
//----------------------
if (useMte) {
MteBase.ArrStatus finalEncodedChunk = mkeEncoder.finishEncrypt();
if (finalEncodedChunk.status != MteStatus.mte_status_success) {
//-----------------------------------------------
// First close inputStream to prevent memory leak
//-----------------------------------------------
inputStream.close();
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
// close inputStream
//-------------------------
output.flush();
inputStream.close();
}
//---------------------------------------------------------------
// 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) {
//-----------
// Update JWT
//-----------
_jwt = serverResponse.access_token;
//----------------
// 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;
}
}
// cont...
// Additional functions and delegate methods for file upload as well
// as the following FileStreamUpload.swift class
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.\n")
exit(EXIT_SUCCESS)
}
func uploadDidFail(message: String) {
print("Server reports that the upload Failed. Error: \(message)\n")
exit(EXIT_FAILURE)
}
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)")
}
}
}
}
#---------------------------------------------------
# Login and Handshake previous to this.
#---------------------------------------------------
# Exerpt from file upload file.
# See full UploadFile.py file for entire code.
#---------------------------------------------------
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
# continued...
/**
* Upload file method
* Allows user to upload files to API
* Must enter in full path to file
*
* Uses MTE MKE Add-on
*/
func UploadFile(clientId string) (out int, err error) {
//------------------------------
// 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 errorPathDoesNotExist, err
}
//---------------------------
// Create MTE MKE from state
encoder := mte.NewMkeEncDef()
defer encoder.Destroy()
if useMte {
encoderStatus := encoder.RestoreStateB64(encoderState)
if encoderStatus != mte.Status_mte_status_success {
errorMessage := "Encoder restore error ("
+ mte.GetStatusName(encoderStatus) + "): "
+ mte.GetStatusDescription(encoderStatus)
fmt.Println(errorMessage)
return int(encoderStatus), errors.New(errorMessage)
}
//---------------------
// Initialize Chunking
encoderStatus = encoder.StartEncrypt()
if encoderStatus != mte.Status_mte_status_success {
errorMessage := "MTE Encoder StartEncrypt error ("
+ mte.GetStatusName(encoderStatus) + "): "
+ mte.GetStatusDescription(encoderStatus)
fmt.Println(errorMessage)
return int(encoderStatus), errors.New(errorMessage)
}
}
//----------------------------
// 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)
//------------------------------------------------------
// If we have an access token add authentication header
if access_token != "" {
// Create a Bearer string by appending string access token
var bearer = "Bearer " + access_token
// Add authorization header to the req
req.Header.Add("Authorization", bearer)
}
req.Header.Set(clientIdHeader, clientId)
req.ContentLength = totalSize
//-----------------
// Process request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err.Error())
return errorReadingResponse, err
} 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 {
errorMessage := "Error back from server: "
+ serverResponse.Message + " Code: " + strconv.Itoa(errorFromServer)
fmt.Println(errorMessage)
return errorFromServer, errors.New(errorMessage)
}
//------------------
// Set access token
access_token = serverResponse.access_token
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 {
errorMessage := "Error base64 decode encoded data: "
+ err.Error() + " Code: " + strconv.Itoa(errorDecodingData)
fmt.Println(errorMessage)
return errorDecodingData, errors.New(errorMessage)
}
encodedData := encodedDatab64[:n]
//----------------------------
// Restore the Decoder state
decoderStatus := decoder.RestoreStateB64(decoderState)
if decoderStatus != mte.Status_mte_status_success {
errorMessage := "Decoder restore error ("
+ mte.GetStatusName(decoderStatus) + "): "
+ mte.GetStatusDescription(decoderStatus)
fmt.Println(errorMessage)
return int(decoderStatus), errors.New(errorMessage)
}
//---------------------
// Initialize Chunking
decoderStatus = decoder.StartDecrypt()
if decoderStatus != mte.Status_mte_status_success {
errorMessage := "MTE Decoder StartDecrypt error ("
+ mte.GetStatusName(decoderStatus) + "): "
+ mte.GetStatusDescription(decoderStatus)
fmt.Println(errorMessage)
return int(decoderStatus), errors.New(errorMessage)
}
//-----------------------------------------------------------
// The response is going to be short don't need to loop
//-----------------------------------------------------------
// Decrypt the chunk
decodedData := decoder.DecryptChunk(encodedData)
if decodedData == nil {
errorMessage := "Decode error."
fmt.Println(errorMessage)
return errorDecodingData, errors.New(errorMessage)
}
//-----------------------
// Finish Decode chunk
finishDecodeChunk, decoderStatus := decoder.FinishDecrypt()
if decoderStatus != mte.Status_mte_status_success {
//--------------------------------------------------------
// Decode finish decrypt unsuccessful and cannot continue
errorMessage := "MTE Decoder FinishDecrypt error ("
+ mte.GetStatusName(decoderStatus) + "): "
+ mte.GetStatusDescription(decoderStatus)
fmt.Println(errorMessage)
return int(decoderStatus), errors.New(errorMessage)
}
//--------------------------------------------------
// Check if there are additional bytes; if so, append
if finishDecodeChunk != nil {
decodedText = append(decodedData[:], finishDecodeChunk[:]...)
} else {
decodedText = decodedData
}
//-------------------
// Save Decoder state
decoderState = decoder.SaveStateB64()
} else {
//-------------------------------
// Base64 Decode response string
decodedText, err = base64.StdEncoding.DecodeString(string(serverResponse.Data))
if err != nil {
errorMessage := "Error base64 decoding string"
fmt.Println(errorMessage)
return errorBase64Decoding, errors.New(errorMessage)
}
}
//-------------------------------
// 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.")
return endProgram, nil
}
}
}
Full Client Side Sample
Here is the entire console side application code.
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.