...

вторник, 23 июля 2013 г.

[Из песочницы] Работаем с реестром запрещенных ресурсов

Автоматизация получения реестра запрещенных ресурсов средствами C#, OpenSSL и фильтрация средствами RouterOS на базе оборудования MikroTik

image


Вводное


В работе будем руководствоваться памяткой оператора связи.

Работа состоит из следующих пунктов:



  1. Создание запроса

  2. Установка и настройка OpenSSL

  3. Подпись запроса

  4. Подача запроса и получение результата обработки запроса

  5. Обработка результата

  6. Добавление в фильтр на MikroTik`е


Создание запроса


Для подачи запроса на получение выгрузки из реестра необходимо прикрепить файл запроса в формате XML. Файл имеет следующий вид:



<?xml version="1.0" encoding="windows-1251"?>
<request>
<requestTime>2012-01-01T01:01:01.000+04:00</requestTime>
<operatorName><![CDATA[Наименование оператора]]></operatorName>
<inn>1234567890</inn>
<ogrn>1234567890123</ogrn>
<email>email@email.ru</email>
</request>



  • requestTime – дата и время формирования запроса с указанием временной зоны;

  • operatorName – полное наименование оператора связи;

  • inn – ИНН оператора связи;

  • ogrn – ОГРН оператора связи;

  • email – электронный адрес технического специалиста, ответственного за использование механизма получения выгрузки; может

    использоваться для оперативной обратной связи в случае возникновения технических вопросов или проблем.


Главное получить время в формате 2012-01-01T01:01:01.000+04:00 и сохранить файл с кодировкой windows-1251.


Код функции генерации запроса на C#:



public static String GeneratingRequest(String operatorName, String inn, String ogrn, String email)
{
String result = "<?xml version=\"1.0\" encoding=\"windows-1251\"?>";
result += "<request><requestTime>";
result += DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz");
result += "</requestTime><operatorName>";
result += "<![CDATA[" + operatorName + "]]>";
result += "</operatorName><inn>";
result += inn;
result += "</inn><ogrn>";
result += ogrn;
result += "</ogrn><email>";
result += email;
result += "</email></request>";

return result;
}


Теперь сохраняем файл в кодировке windows-1251:



String Request = GeneratingRequest("Наименование оператора", "1234567890", "1234567890123", "email@email.ru")
StreamWriter swRequest = new StreamWriter(@"C:\request.xml", false, Encoding.GetEncoding("Windows-1251"));
swRequest.Write(Request);
swRequest.Close();


После создания запроса нам необходимо его подписать. Для этого воспользуемся криптографическим пакетом с открытым исходным кодом для работы с SSL/TLSOpenSSL . Установим и настроем.


OpenSSL




Качаем пакет с этого сайта — slproweb.com. В моем случае это был Win64 OpenSSL v1.0.1e.

Да эта сборка требует для работы установленного Visual C++ 2008 Redistributables, который можно скачать там же.

Устанавливаем. При установке в диалоге «Select Additional Tasks» следует выбрать «The OpenSSL” binaries (/bin) directory» более хитростей нет.

Далее переходим в папку куда установили: C:/OpenSSL/bin и редактируем файл openssl.cfg. В начало файла добавим:


openssl_conf = openssl_def


В конце:

[openssl_def]

engines=engine_section


[engine_section]

gost=gost_section


[gost_section]

engine_id=gost

dynamic_path = C:/OpenSSL/bin/gost.dll

default_algorithms=ALL


Все практически уже работает осталось настроить переменные среды:

OPENSSL_CONF = C:/OpenSSL/bin/openssl.cfg — полный путь к openssl.cfg

ну и в PATH += C:/OpenSSL/bin;


Теперь нам нужна ЭЦП. Приобрести ее можно в доверенном удостоверяющем центре. Ключ необходимо экспортировать в формате PKCS#12 из криптоконтейнера в Windows с помощью утилиты P12FromGostCSP


Далее преобразовать его в PEM. В OpenSSL это делается так (через командную строку — может запросить пароль которым защищен Ключ PKCS#12):



openssl.exe pkcs12 -in C:/key.pfx -out C:/key.pem -nodes -clcerts


image


Все теперь мы в состоянии подписать наш запрос.


Подпись запроса


Подписать запрос можно через OpenSSL следующей командой:



openssl.exe smime -sign -in C:/request.xml -out C:/request.xml.sign -signer C:/key.pem -outform DER


image


Реализация функции подписи файла на C#:



public static Boolean SignRequest()
{
Boolean ret = true;

String OpenSSLPath = @"C:\OpenSSL\bin";
String RequestPath = @"C:\request.xml";
String SignRequestPath = @"C:\request.xml.sign";
String KeyPEMPath = @"C:\key.pem";

try
{

Process cmdProcess = new Process();

/*
* Строку ниже можно убрать
* если переменная среды PATH
* имеет путь до OpenSSL
*/
cmdProcess.StartInfo.WorkingDirectory = OpenSSLPath;
cmdProcess.StartInfo.FileName = "openssl.exe";
cmdProcess.StartInfo.Arguments = String.Format("smime -sign -in {0} -out {1} -signer {2} -outform DER", RequestPath, SignRequestPath, KeyPEMPath);

cmdProcess.StartInfo.CreateNoWindow = true;
cmdProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
cmdProcess.Start();
//Задержка необходима т.к. нужно дождаться конца операции
Thread.Sleep(2500);
}
catch (Exception)
{
ret = false;
}

return ret;
}


Теперь проверим нашу работу. Подтверждение подлинности ЭП: gosuslugi.ru, выбираем — Подтверждение подлинности электронного документа. ЭП — отсоединенная, в формате PKCS#7


Переходим непосредственно к запросу дампа реестра запрещенных ресурсов.


Подача запроса и выгрузка реестра


В ручную все просто: Форма подачи запроса, отправляем файл запроса и его подпись (C:/request.xml и C:/request.xml.sign)


image


Если все нормально то результатом будет — Идентификатор запроса, с помощью его можно проверять результат обработки запроса. Как правило если все нормально то реестр будет выгружен минут через 5 в ZIP формате в архиве будет два файла dump.xml — дамп реестра и его цифровая подпись.


Теперь автоматизируем данный процесс. Для выгрузки есть сервис работающий по протоколу доступа SOAP , адрес: сервиса. WSDL схема доступна по адресу: WSDL схема .


Сервис состоит из 3-х методов:



  • getLastDumpDate

    Метод предназначен для получения временной метки последнего обновления выгрузки из

    реестра, long формат, так и не понял как разобрать эту дату, храню как есть, если кто в курсе, то прокомментируйте.

  • sendRequest

    Метод предназначен для направления запроса на получение выгрузки из реестра, Принимает requestFile и signatureFile в base64Binary формате — файл запроса и его подписи (C:/request.xml и C:/request.xml.sign) В ответ присылает result — Результат обработки запроса в boolean формате и если все удачно, то code — Идентификатор запроса, строка по которой необходимо получить выгрузку из реестра в формате string. Так же resultComment — Комментарий к результату обработки запроса в формате string.

  • getResult

    Метод предназначен для получения результата обработки запроса — выгрузки из реестра, принимает code — Идентификатор запроса, строка по которой необходимо получить выгрузку из реестра в формате string. В ответ присылает result — Результат обработки запроса в boolean формате и если все удачно, то registerZipArchive — Файл zip-архив с выгрузкой из реестра в формате base64Binary. Так же resultComment — Комментарий к результату обработки запроса в формате string.


Логика проста: Получаем временную метку последнего обновления реестра, если он изменилась, то направляем запрос на получение выгрузки из реестра, если все удачно, то через 5 минут забираем наш реестр или ждем еще чуть чуть…


Добавляем в ресурсы сервис, передавая адрес WSDL схемы.

Код функций getLastDumpDate, sendRequest, getResult



public static Int64 LastDumpDate()
{
Int64 lastDumpDate = 0;

using (ChannelFactory<ServiceReference.OperatorRequestPortType> scf = new ChannelFactory<ServiceReference.OperatorRequestPortType>(
new BasicHttpBinding(), new EndpointAddress("http://zapret-info.gov.ru/services/OperatorRequest/")))
{
ServiceReference.OperatorRequestPortType channel = scf.CreateChannel();
ServiceReference.getLastDumpDateResponse glddr = channel.getLastDumpDate(new ServiceReference.getLastDumpDateRequest());
lastDumpDate = glddr.lastDumpDate;
}

return lastDumpDate;
}

public static Boolean SendRequest(out String resultComment, out String code, Byte[] requestFile, Byte[] signatureFile)
{
Boolean result = false;
code = null;

using (ChannelFactory<ServiceReference.OperatorRequestPortType> scf = new ChannelFactory<ServiceReference.OperatorRequestPortType>(
new BasicHttpBinding(), new EndpointAddress("http://zapret-info.gov.ru/services/OperatorRequest/")))
{
ServiceReference.OperatorRequestPortType channel = scf.CreateChannel();
ServiceReference.sendRequestRequestBody srrb = new ServiceReference.sendRequestRequestBody();

srrb.requestFile = requestFile;
srrb.signatureFile = signatureFile;

ServiceReference.sendRequestResponse srr = channel.sendRequest(new ServiceReference.sendRequestRequest(srrb));

resultComment = srr.Body.resultComment;

if (result = srr.Body.result)
{
code = srr.Body.code;
}
}

return result;
}

public static Boolean GetResult(out String resultComment, out Byte[] registerZipArchive, String code)
{
Boolean result = false;
registerZipArchive = null;

using (ChannelFactory<ServiceReference.OperatorRequestPortType> scf = new ChannelFactory<ServiceReference.OperatorRequestPortType>(
new BasicHttpBinding(), new EndpointAddress("http://zapret-info.gov.ru/services/OperatorRequest/")))
{
ServiceReference.OperatorRequestPortType channel = scf.CreateChannel();
ServiceReference.getResultRequestBody grrb = new ServiceReference.getResultRequestBody();

grrb.code = code;

ServiceReference.getResultResponse grr = channel.getResult(new ServiceReference.getResultRequest(grrb));

resultComment = grr.Body.resultComment;

if (result = grr.Body.result)
{
registerZipArchive = grr.Body.registerZipArchive;
}
}

return result;
}


Например отправим запрос:



String resultComment, code;
if(SendRequest(out resultComment, out code, File.ReadAllBytes(@"C:/request.xml"), File.ReadAllBytes(@"C:/request.xml.sign")))
{
//... Все удачно
}


Разбираем дамп реестра


Надо распаковать архив:



// Byte[] registerZipArchive - Получен при удачной выгрузке GetResult();
File.WriteAllBytes(@"C:/register.zip", registerZipArchive);
ZipFile.ExtractToDirectory(@"C:/register.zip", @"C:/register");


Парсим XML



Для этого создадим два класса: первый RegisterDump будет содержать поле UpdateTime и список объектов content, Второй ItemRegisterDump представляет один объект content со всеми полями.

public class RegisterDump
{
/*
* <reg:register updateTime="2013-07-15T10:05:00+04:00" xmlns:reg="http://rsoc.ru" xmlns:tns="http://rsoc.ru">
* <content></content>
* <content></content>
* ...
* <content></content>
* </reg:register>
*/

public List<ItemRegisterDump> Items { get; set; }
public String UpdateTime { get; set; }

public RegisterDump()
{
this.Items = new List<ItemRegisterDump>();
this.UpdateTime = String.Empty;
}

public RegisterDump(String UpdateTime, List<ItemRegisterDump> Items)
{
this.Items = Items;
this.UpdateTime = UpdateTime;
}
}

public class ItemRegisterDump
{
/*
* <content id="60" includeTime="2013-01-12T16:33:38">
* <decision date="2013-11-03" number="МИ-6" org="РосКосМопсПопс"/>
* <url><![CDATA[http://chelaxe.ru/blacklist/]]></url>
* <domain><![CDATA[chelaxe.ru]]></domain>
* <ip>123.45.67.89</ip>
* </content>
*/

public String id { get; set; }
public String includeTime { get; set; }

public String date { get; set; }
public String number { get; set; }
public String org { get; set; }

public String url { get; set; }
public String domain { get; set; }
public String ip { get; set; }

public ItemRegisterDump()
{
id = String.Empty;
includeTime = String.Empty;

date = String.Empty;
number = String.Empty;
org = String.Empty;

url = String.Empty;
domain = String.Empty;
ip = String.Empty;
}
}


Парсим:



RegisterDump Register = new RegisterDump();
String dumpfile = @"C:/register/dump.xml";

XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(dumpfile);

Register.UpdateTime = xmlDoc.GetElementsByTagName("reg:register")[0].Attributes.GetNamedItem("updateTime").InnerText;
XmlNodeList content = xmlDoc.GetElementsByTagName("content");

for (int i = 0; i < content.Count; i++)
{
XmlNodeList nodechild = content[i].ChildNodes;

ItemRegisterDump item = new ItemRegisterDump();

item.id = content[i].Attributes.GetNamedItem("id").InnerText;
item.includeTime = content[i].Attributes.GetNamedItem("includeTime").InnerText;

item.date = nodechild[0].Attributes.GetNamedItem("date").InnerText;
item.number = nodechild[0].Attributes.GetNamedItem("number").InnerText;
item.org = nodechild[0].Attributes.GetNamedItem("org").InnerText;

item.url = nodechild[1].InnerText;
item.domain = nodechild[2].InnerText;
item.ip = nodechild[3].InnerText;

Register.Items.Add(item);
}


Теперь блокируем все это добро на нашем MikroTik.


Блокируем средством MikroTik




Делаем мы это с помощью layer7-protocol и добавляем в фильтры.

Вот пример:

/ip firewall layer7-protocol add name=12 comment=register regexp=^.+(chelaxe.ru).*$

/ip firewall filter add action=drop chain=forward disabled=no dst-port=80 layer7-protocol=12 protocol=tcp src-address=192.168.0.0/24 comment=register


image

image

image

image


Все теперь все пакеты для подсети 192.168.0.0/24 приходящие по TCP на 80 порт с содержанием подстроки chelaxe.ru отбрасываются.

В комментарии надпись register добавляется не с проста. Она нужна нам будет для удаления всех правил прежде чем добавить обновленные.


Вот скрипт который удалит все записи:


/ip firewall layer7-protocol remove [find comment=register]

/ip firewall filter remove [find comment=register]


image


Общение с MikroTik роутером будет посредством API, необходимую библиотеку с примерами нам уже написали: wiki/API C#


Используя эту библиотеку (класс) и принцип блокирования контента разобранный выше реализуем все это на C#



public static Boolean AddFilterL7(String ip, String username, String password, RegisterDump dump, String SRCAddress)
{
Boolean ret = true;

try
{
//Класс MK смотри здесь http://wiki.mikrotik.com/wiki/API_in_C_Sharp
MK mikrotik = new MK(IPAddress.Parse(ip).ToString());

if (mikrotik.Login(username, password))
{
mikrotik.Send("/system/script/add");
mikrotik.Send("=name=cleaner");
mikrotik.Send("=source=/ip firewall layer7-protocol remove [find comment=register]\n/ip firewall filter remove [find comment=register]", true);

mikrotik.Send("/system/script/run");
mikrotik.Send("=number=cleaner", true);

/* Cleaner
* /ip firewall layer7-protocol remove [find comment=register]
* /ip firewall filter remove [find comment=register]
*/

//Ждем пока все старые записи удалятся
Thread.Sleep(1000);

foreach (ItemRegisterDump item in dump.Items)
{
//Ждем немного чтобы MikroTik не захлебнулся
Thread.Sleep(100);

// /ip firewall layer7-protocol add name=12 comment=register regexp=^.+(chelaxe.ru).*$
mikrotik.Send("/ip/firewall/layer7-protocol/add");
mikrotik.Send("=name=" + item.id);
mikrotik.Send("=comment=register");
mikrotik.Send("=regexp=^.+(" + item.domain + ").*$", true);

//Ждем немного чтобы MikroTik не захлебнулся
Thread.Sleep(100);

// /ip firewall filter add action=drop chain=forward disabled=no dst-port=80 layer7-protocol=12 protocol=tcp src-address=192.168.0.0/24 comment=register
mikrotik.Send("/ip/firewall/filter/add");
mikrotik.Send("=action=drop");
mikrotik.Send("=chain=forward");
mikrotik.Send("=disabled=no");
mikrotik.Send("=dst-port=80");
mikrotik.Send("=layer7-protocol=" + item.id);
mikrotik.Send("=protocol=tcp");
mikrotik.Send("=src-address=" + SRCAddress);
mikrotik.Send("=comment=register", true);
}
}

Thread.Sleep(200);
mikrotik.Close();
}
catch (Exception)
{
ret = false;
}

return ret;
}


Ну вот и все.


Исходники доступны здесь.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


Комментариев нет:

Отправить комментарий