Промежуточная среда .NET Remoting
В качестве примера модификации рассмотрим добавление шифрования в стандартный канал на основе симметричного шифрования. Симметричное шифрования выбрано в данном примере исключительно из за простоты реализации. На практике следовало бы скорее использовать для данной цели шифрование с открытом ключом, однако действительно правильным решением обеспечения безопасности передачи данных в Remoting заключается либо в использовании IIS, либо в развертывании VPN на основе безопасного решения (например, OpenVPN). Поэтому данную модификацию канала следует рассматривать исключительно как пример по модернизации инфраструктуры .NET Remoting.
Для реализации шифрования достаточно добавить в цепочку поставщиков труб потока дополнительного поставщика, который будет создавать трубу, шифрующую и дешифрующую проходящие по ней потоки с сериализованными сообщениями. Для добавления такого поставщика в канал следует использовать файл конфигурации. Для использовании стандартного канала с дополнительной трубой достаточно указать имя класса поставщика трубы в разделе <channel><clientProviders><provider>.
В нижеследующем файле описаны классы поставщика трубы клиента и самой трубы, а также поставщика трубы сервера и его трубы. Сам механизм шифрования на основе стандартных классов FCL описан в "Применение промежуточных сред" .
// Файл SevaRemotingEncrypted.cs
using System;
using System.IO;
using System.Collections;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Messaging;
using Seva.Security.Encryption;
namespace Seva.Remoting.Encryption
{
class EncryptedClientChannelSinkProvider: IClientChannelSinkProvider
{
private IClientChannelSinkProvider next;
public IClientChannelSinkProvider Next
{
get { return next; }
set { next = value; }
}
private SymmetricEncryptor encryptor;Конструктор поставщика трубы на стороне клиента получает в качестве аргумента словарь свойств, заполненный свойствами из файла конфигурации. Это позволяет указывать имя файла с ключом шифрования в файле конфигурации, а в конструкторе создается симметричный шифровальщик с указанным ключом.
public EncryptedClientChannelSinkProvider(IDictionary properties,
ICollection providerData)
{
string keyFile = (string) properties["key"];
Console.WriteLine("Client key: [{0}]", keyFile);
encryptor = new SymmetricEncryptor(keyFile);
}Метод CreateSink является основным методом поставщика трубы. В типичном случае сначала вызывается этот же метод для следующего в цепочке поставщика, а затем создается труба, вставляемая в цепочку труб. Следует отметить, что данный поставщик не может быть последним в цепочке, но соответствующие проверки свойства Next и выбросы исключений для экономии места не показаны.
public IClientChannelSink CreateSink(IChannelSender channel,
string url, object remoteChannelData)
{
IClientChannelSink next = Next.CreateSink(channel,
url, remoteChannelData);
return new EncryptedClientChannelSink(encryptor, next);
}
}Класс поставщика трубы на стороне сервера устроен аналогичным образом.
class EncryptedServerChannelSinkProvider : IServerChannelSinkProvider
{
private IServerChannelSinkProvider next;
private SymmetricEncryptor encryptor;
public IServerChannelSinkProvider Next
{
get { return next; }
set { next = value; }
}
public EncryptedServerChannelSinkProvider(IDictionary properties,
ICollection providerData)
{
string keyFile = (string) properties["key"];
Console.WriteLine("Server key: [{0}]", keyFile);
encryptor = new SymmetricEncryptor(keyFile);
}
// Создание трубы канала
public IServerChannelSink CreateSink(IChannelReceiver channel)
{
IServerChannelSink nextSink = Next.CreateSink(channel);
return new EncryptedServerChannelSink(channel, encryptor, nextSink);
}
// Обязательный метод интерфейса
public void GetChannelData(IChannelDataStore channelData)
{
}
}
Листинг
8.1.
Собственно шифрование выполняется в созданными поставщиками трубах канала на стороне клиента и сервера.
class EncryptedServerChannelSink : IServerChannelSink
{
public IDictionary Properties
{
get { return null; }
}
private IServerChannelSink nextSink;
public IServerChannelSink NextChannelSink
{
get { return nextSink; }
}
private SymmetricEncryptor encryptor;
public EncryptedServerChannelSink(IChannelReceiver channel,
SymmetricEncryptor channelEncryptor, IServerChannelSink next)
{
encryptor = channelEncryptor;
nextSink = next;
}Главным методом трубы при синхронном удаленном вызове является метод ProcessMessage. В случае трубы потока данный метод может оперировать как с самим сообщением, так и с полученным из него в ходе сериализации потоком. Труба шифрования сервера должна дешифровать этот поток при получении сообщения от клиента и зашифровать поток при посылке клиенту ответа сервера.
// Метод синхронной обработки сообщения
public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
IMessage requestMsg, ITransportHeaders requestHeaders,
Stream requestStream, out IMessage responseMsg,
out ITransportHeaders responseHeaders, out Stream responseStream)
{
Stream plainStream = null;
MemoryStream decodedStream = new MemoryStream();
requestStream = Utils.ReadAllStream(requestStream);
// Расшифровать запрос клиента
encryptor.Decrypt(requestStream, decodedStream);
decodedStream.Position = 0;
ServerProcessing result = nextSink.ProcessMessage(sinkStack,
requestMsg, requestHeaders, decodedStream, out responseMsg,
out responseHeaders, out plainStream);
// Зашифровать ответ сервера
responseStream = new MemoryStream();
encryptor.Encrypt(plainStream, responseStream);
responseStream.Position = 0;
//
return result;
}
public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack,
Object state, IMessage msg, ITransportHeaders headers, Stream stream)
{
throw new NotSupportedException();
}
public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack,
Object state, IMessage msg, ITransportHeaders headers)
{
return nextSink.GetResponseStream(sinkStack, state, msg, headers);
}
} // EncryptedServerChannelSink
Листинг
8.2.