Hi!
We have an important project in our company whose goal is to perform ON TRANSIT encryption between the client and the backend.
The type of encryption we use is RSA 4K.
The encryption on the client side is already done nicely.
Also for decoding we already have the classes responsible for this and they work fine.
Our code is written in c# and we reveal to the client a long list of web services.
Every web service is written in WCF architecture (old, yes, and we want to get out of it but that's the situation at the moment).
My question is this:
We have hundreds of APIs in the system.
I am looking for a generic engine that knows how to catch the payload,
decrypt it, and then when the API starts working, the payload that reaches it will already be decrypted.
Here is what I cam up with:
I created an attribute class called "Something" (strange name, just for now):
namespace Journeys
public sealed class SomethingAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
if (channelDispatcher != null)
{
foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomMessageInspector());
}
}
}
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
{
}
}
As you can see, SomethingAttribute is implementing CustomMessageInspector class:
namespace Company.S2S.OnTransit
{
public class CustomMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
try
{
MessageBuffer buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
var copy = buffer.CreateMessage();
XmlDictionaryReader bodyReader = copy.GetReaderAtBodyContents();
bodyReader.ReadStartElement("Binary");
byte[] bodyBytes = bodyReader.ReadContentAsBase64();
string messageBody = Encoding.UTF8.GetString(bodyBytes);
Log.LogThis($"CustomMessageInspector: messageBody is: {messageBody}", eloglevel.debug);
string decryptedPayload = DecryptPayload(messageBody);
Log.LogThis($"CustomMessageInspector: dec payload is: {decryptedPayload}", eloglevel.debug);
using (var memoryStream = new MemoryStream())
{
var writer = new StreamWriter(memoryStream);
writer.Write(decryptedPayload);
writer.Flush();
memoryStream.Position = 0;
var byteArray = SerializeMemoryStreamToByteArray(memoryStream);
request = CreateNewMessageBinary(byteArray, request, request.Version);
}
return null;
}
catch (Exception ex)
{
Generic.LogException(ex, "AfterReceiveRequest");
throw;
}
}
private byte[] SerializeMemoryStreamToByteArray(MemoryStream memoryStream)
{
byte[] byteArray = new byte[memoryStream.Length];
memoryStream.Read(byteArray, 0, byteArray.Length);
return byteArray;
}
private Message CreateNewMessageBinary(byte[] byteArray, Message request, MessageVersion version)
{
Message newMessage = Message.CreateMessage(version, null, new BinaryReader(new MemoryStream(byteArray)));
newMessage.Headers.CopyHeadersFrom(request.Headers);
newMessage.Properties.CopyProperties(request.Properties);
return newMessage;
}
}
}
Here is an example of one of our web service (all of them looks like this).
Please see that JourneysEngine is now implementing the [Something] attribute.
namespace Journeys
{
[ServiceContract(Namespace = "https://www.company.net")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[Something]
public class JourneysEngine
{
#region REST APIs
[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "POST", UriTemplate = "/GetJourneys")]
[OperationContract]
public Stream GetJourneys(Stream stream)
{
}
}
}
In a nutsheel, before the code hits the API, it pass through CustomMessageInspector.AfterReceiveRequest.
There, I can grab the Message and decode it.
The problem is here, when I hit the API - I'm getting the following exception:
### Exception details - System.IO.IOException:
An exception has been thrown when reading the stream. --->
System.Runtime.Serialization.InvalidDataContractException:
Type 'System.IO.BinaryReader' cannot be serialized.
Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized
with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute.
See the Microsoft .NET Framework documentation for other supported types.
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.ThrowInvalidDataContractException(String message, Type type)
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)
at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle,
Type type) at System.Runtime.Serialization.DataContractSerializer.get_RootContract()
at System.Runtime.Serialization.DataContractSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph,
DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph,
DataContractResolver dataContractResolver)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.ServiceModel.Channels.XmlObjectSerializerBodyWriter.OnWriteBodyContents(XmlDictionaryWriter writer)
at System.ServiceModel.Channels.BodyWriterMessage.OnWriteBodyContents(XmlDictionaryWriter writer)
at System.ServiceModel.Channels.Message.OnGetReaderAtBodyContents()
at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Read(Byte[] buffer, Int32 offset, Int32 count)
--- End of inner exception stack trace ---
at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.IO.Stream.InternalCopyTo(Stream destination, Int32 bufferSize)
at CallVU.Utils.Extensions.StreamExtensions.Stringify(Stream stream, Boolean print)
at Journeys.JourneysEngine.GetJourneys(Stream stream)
The Stringify method is an extension method (who is working great and for a long time that just takes the Stream passes to the API and return it as a string:
public static string Stringify(this Stream stream, bool print = false)
{
Log.LogThis($"Stringify Started", eloglevel.trace);
if (stream == null)
{
Log.LogThis($"Stream is null, return string empty", eloglevel.warn);
return "";
}
bool canSeek = stream.CanSeek;
if (!canSeek)
{
Log.LogThis($"cant seek in stream, copying it.", eloglevel.trace);
using (MemoryStream memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
memoryStream.Position = 0;
string response = Encoding.UTF8.GetString(memoryStream.ToArray());
if (print) Log.LogThis($"stream as string: ({response})", eloglevel.trace);
return response;
}
}
using (StreamReader reader = new StreamReader(stream, Encoding.UTF8, true, 1024, true))
{
string result = reader.ReadToEnd();
stream.Position = 0;
if (print)
{
Log.LogThis($"Stream is: {result}", eloglevel.trace);
}
else
{
Log.LogThis($"First 10 chars of stream are: {result.CutByLength(10)}", eloglevel.trace);
}
return result;
}
}
Just in case, here is the start of the API:
[WebInvoke(ResponseFormat = WebMessageFormat.Json, Method = "POST", UriTemplate = "/GetJourney")]
[OperationContract]
public Stream GetJourney(Stream stream)
{
(string caller, string myName) = Generic.InitMethod(setThreadName: true);
try
{
string json = stream.Stringify();
NLogger.Instance.Info($"{myName} - Start with the following payload: {json}");
GetJourneyModel model = JsonHelper.DeserializeJson<GetJourneyModel>(json);
}
What I have tried:
I tried to implement the above code from the web.config without any luck what so ever