CRM-система для УК и ТСЖ

Поддержка длинных строк

Будете спамить рекламой - будем нещадно банить)))
Сообщение
Автор
two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#1 Сообщение two_oceans » 09 дек 2016, 05:58

Тема больше эмоции, чем реально обсуждение. За ближайшие дни наметилась таки самая маленькая продвижка в сторону библиотеки подписания - вроде разобрался как получать хэш от КриптоПро через WinCryptApi. вдохновленный этим нашел кучу примеров подписей и решил что пора бы попроверять хотя бы хэши. Для этого нужно разобраться с длинными строками - обычных shortstring не хватает на закодированный сертификат. Попробовал встроенные типы - ansistring - вроде бы все замечательно, но при каждом изменении строка прыгает на другой адрес памяти, периодически (при обращении к символу внутри строки особенно) роняя всю программу (самое прикольное, что IDE тоже роняется). Да еще и не преобразуется в shortstring - это мне не понравилось. Как водится подумал - я напишу-ка свой модуль работы с длинными строками, с блек-джеком и ... ну, вы поняли.
Сказано - сделано. За основу взял старый модуль для строк длиной 260 символов, переименовал, переопределил размер. Но там были простые строки без длины и определения операторов. Определил дополнительно к бывшим новый тип как запись - в одном поле длина, в другом собственно массив символов нужной длины. Написал базовые процедуры вроде lstr_Assign/lstr_Concat (передача параметров-строк по ссылке естественно), разобрался с операторами (простое присваивание компилятор сам делает копированием памяти и не дает переопределить, зато можно присваивание между типами доопределить/поменять). Все так красиво написал, определил разные размеры строк (100Кб, 1Мб, 20 Мб), но смутило, что в примерах операторов параметры и результат не по ссылкам.
Выбрал оператор присваивания где только shortstring в параметрах и протестил. По результатам, захотелось долго материться. Действительно, компилятор попробовал протащить результат (строку 100 Кб) через стэк. Естественно это не удалось - на x86 стэк только 64 Кб (сами авторы компилятора пометили, что это из-за инструкции ret процессора и они не виноваты). Пока не нашел как помешать протаскиванию результата через стэк. Покрутил и так и этак - количество параметров и наличие результата у определенного оператора похоже зашито на уровне синтаксического транслятора. Пока не теряю надежду отыскать какую нибудь полезную директиву, если у кого было такое подскажите.
Аналогично нельзя объявлять такую переменную внутри функций - стек тоже переполняет. Объявления локальных shortstring пока оставил, но видимо и их надо поубирать - рано или поздно накроют стек.
Зато попутно выяснилось что можно на выход оператора выдать указатель. Но, этот указатель совершенно ничего "не знает" о переменной куда будет выдан (логично блин), поэтому под него надо выделить память внутри оператора присваивания. Внешне (в программе) это будет выглядеть как

Код: Выбрать все

type plstr100k=^longstr100k;
var p:plstr100k;
begin
  p:='';
  writeln(p.str);
  Free(p);
end.
Мда... вообще маразм строка присваивается указателю и это работает! вот как по такому догадаться, что при присваивании была выделена память и ее нужно освободить.
Полез в исходники как определено присваивание в ansistring... ОМГ, недостижимый уровень извращенства - тип прописан в исходниках компилятора, вместо присваивания всех длинных типов (строки, массивы, записи, объекты, вариант) выполняется процедура копирования памяти плюс индивидуально для каждого типа считаются ссылки на память и прочее. То есть чтобы ввести новый длинный тип "красиво" нужно добавить код типа в исходник компилятора, потом его везде учесть, потом откомпилировать свою новую версию компилятора. Что называется если начал - то делай по полному, а иначе будут торчать "уши" вроде lstr_Assign или Free.
Переписывать компилятор я конечно не стану - не такого блек-джека я хотел. Пока потестил такую версию, где можно присвоить строку указателю. Операторы поменял, чтобы входящие параметры по ссылкам, результат указателем. В общей сложности на все эти "исследования" ушли сутки. Работает, собака. Только надо не забыть потом везде освобождение памяти.

RatWar
Новичок
Сообщений: 6
Зарегистрирован: 11 авг 2016, 08:37
Поблагодарили: 12 раза

Поддержка длинных строк

#2 Сообщение RatWar » 22 дек 2016, 12:55

А я начал делать библиотеку подписания через OpenSSL.
Хэш считаю так:

Код: Выбрать все

function GOSTR3411(const msg: string): string;
var
  inBuf, outBuf: array[0..4095] of Char;
  mdctx: EVP_MD_CTX;
  mdValue: array[0..EVP_MAX_MD_SIZE] of byte;
  mdLength: Cardinal;
  b64Length: Integer;
  memout, Base64: pBIO;
begin
  StrPCopy(inbuf, msg);
  EVP_DigestInit(@mdctx, EVP_get_digestbyname(PChar('md_gost94')));
  EVP_DigestUpdate(@mdctx, @inbuf, StrLen(inbuf));
  EVP_DigestFinal(@mdctx, @mdValue, mdLength);
  Base64 := BIO_new(BIO_f_base64); // BIO типа base64
  memout := BIO_new(BIO_s_mem);
  Base64 := BIO_push(Base64, memout);
  BIO_write(Base64, @mdValue, mdLength);
  BIO_flush(Base64);
  b64Length := BIO_read(memout, @outbuf, 4096);
  outbuf[b64Length - 1] := #0;
  Result := StrPas(@outbuf);
end;


Хэш сертификата можно один раз подсчитать через вызов в консоли:

Код: Выбрать все

function GetDigest(const Text: string): string;
begin
  GetDosOutput('openssl dgst -binary -md_gost94 |openssl base64', Text, 30, Result);
  Delete(Result, Length(Result), 1);
end;

two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#3 Сообщение two_oceans » 22 дек 2016, 13:33

Благодарю. До OpenSSL у меня тоже в планах добраться, так что это пригодится. Пока еще не придумал как поудобнее хранить сертификат, чтобы подходил и к WinCryptoApi и к OpenSSL. Да, серьезно, где-то читал дескать WinCryptApi только с хранилища берет сертификаты и закрытые ключи. Однако после беглого просмотра документации похоже что это не совсем так и сертификат можно и из файла зацепить. Буду еще разбираться так ли все это.
Логично, что хэш сертификата понадобится вычислить один раз, но кроме того понадобятся и реквизиты сертификата - как минимум, серийный номер и издатель. Встает выбор хранить это где-то или каждый раз вытаскивать из сертификата. Случаев когда нужно сразу "мульён" запросов за раз подписать немного, значит либо реквизиты плюс хэш будут висеть в кэше в памяти/на диске, где их нужно будет как-то отличать (а вдруг мы на 1001 раз решили подписать другим сертификатом) либо 1001 раз его считать.
Еще один момент - надо посмотреть понадобится ли "переворачивать" хэш после OpenSSL (в смысле преобразовать Little Endian - Big Endian до кодирования в Base64). Если понадобится, то командная строка неверна.
По поводу вычисления в консоли - так конечно можно, но если потом устанавливать соединение к интернету этой же программой для отправки подписанного, то увы есть шанс, что антивирусы заклеймят трояном. В этом смысле из скриптов vbscript, php, perl или python вызывать консоль проще - их антивирус проверяет по другому.

Ну и отчитаюсь по длинным строкам - написал базовые обобщения операций, теперь наделать новых типов разной длины стало проще. Также уже могу удалить тег подписи из XML и вытащить значение хэша из подписанного файла. Все длинными строками. Пока меня отвлекли на другую работу, так что дело идет медленно.

RatWar
Новичок
Сообщений: 6
Зарегистрирован: 11 авг 2016, 08:37
Поблагодарили: 12 раза

Поддержка длинных строк

#4 Сообщение RatWar » 22 дек 2016, 14:34

Предполагаю один раз взять данные сертификата, записать в инифайл и если нужно беру их оттуда
Серийник берется просто:
function SerialNumber(const FileName: string): string;
var
x: pX509;
begin
x := X509(FileName);
Result := BN_bn2dec(ASN1_INTEGER_to_BN(X509_get_serialNumber(x), nil));
X509_free(x);
end;

Издателя сложнее, но получилось также вытащить, пока остановился на каноникализации.

Отправлено спустя 2 минуты 15 секунды:
Забыл еще, так загружаю сертификат

Код: Выбрать все

function X509(const f: string): pX509;
var
  bio_cert: pBIO;
  k: pX509;
begin
  k := X509_new;
  bio_cert := BIO_new_file(PChar(f), 'rb');
  Result := PEM_read_bio_X509(bio_cert, k, nil, nil);
  BIO_free(bio_cert);
end;


Отправлено спустя 2 минуты 3 секунды:
Использую документ Криптоком
Библиотека libcrypto.
Руководство программиста

Отправлено спустя 2 минуты 53 секунды:
В консоль обращаюсь через StdIn и StdOut, у меня на машине лиценз. DrWeb не обращает внимания

two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#5 Сообщение two_oceans » 23 дек 2016, 07:08

Благодарю. Инифайл это как бы сказать.. наводит мысль о временах Windows 3.1 - начале 90х. Если сертификатов будет много - получится неудобно. Наверно не нужно говорить, что инифайлы игнорируют одинаковые параметры внутри одной секции, значит придется делать кучу секций либо добавлять циферки (как в инифайле OpenSSL). По поводу хранения я скорее думал в направлении file of <record type>.

Еще мысль - а не нужно ли криптоданные специально затирать перед освобождением буфера? Или OpenSSL это делает при освобождении? Можно конечно понадеяться на OpenSSL, но там уже находили кучу багов по данному направлению (получалась память и в ней были незатертые ключи) и потому я бы перестраховался. Теоретически, если память потом выделится другому приложению это может создать угрозу безопасности.

Извлечение номера хорошо бы проверить на разных сертификатах. Поясню, некоторые удостоверяющие центры выдают номера последовательно и номер короткий вроде 2a 4b de, у других же номер длинный-предлинный (сгенерирован случайно либо меняется середина). Конечно, shortstring 255 символов должно хватить, но хорошо бы проверить что при промежуточных преобразованиях ничего не теряется. А вот с издателем и 255 может не хватить - у меня есть сертификаты, где dn издателя- 218 символов и полагаю это не предел. Еще возможно понадобится "отпечаток" (идентификатор) сертификата. По отпечаткам удобно строить цепочки при проверке.

С чтением сертификата средствами OpenSSL тоже не так все гладко. Я конвертировал уже несколько контейнеров КриптоПро в формат p12 (через P12FromGostCSP) и в формат pem (через privkey)- но при операциях объединения в p12 (или импорта из p12 или подписания "склеенным" pem-сертификатом c pem-закрытым ключом) на данных из новых контейнеров КриптоПро (с позапрошлогодними все ок, но они истекли - какая досада) OpenSSL с постоянством выдает что-то вроде asn1 tag too long, при этом позволяет подписать с указанием отдельного файла сертификата и отдельного файла закрытого ключа. Вообще хотелось бы этого избежать и использовать один файл, а не маяться с двумя.
У меня стоит лицензионный KAV, ну тоже конечно бесит меня излишними проверками и рекламой KIS, но после отключения доброй половины функций - жить можно. Отключаю так много, потому что могу большинство вирусов вручную "забороть", но без подстраховки антивирусом как-то неуютно.
Честно говоря, DrWeb не очень доверяю как раз из-за таких ложных срабатываний, когда он написанные мной программы определял как вирусы на этапе компиляции и удалял. Хорошо если удалял, когда просто блокировал - было гораздо проблемнее - невозможно просто отключить антивирус, добавить исключение и перекомпилировать еще разок, так как блокируется не службой, а драйвером ядра, а такой драйвер не останавливается без перезагрузки. Может быть такое поведение исправили, но скорее всего нет - это проблема не ядра антивируса, не лицензии, а скорее не очень прямых рук составителей вирусной базы DrWeb (не могут подобрать минимальный набор признаков трояна) и судя по тому что это повторяется из версии в версию - вряд ли они стали прямее. Ну или может у них такая идеология, что лучше перебдить чем недобдить :)
Полагаю, на данный момент библиотека не обращается к Интернету и не пишет файлы, поэтому второе-третье условие определения трояна не выполняется. Однако как только программа создающая дочерние exe процессы обратится к интернету и начнет что-то записывать оттуда - антивирус вполне может ее "заклеймить" трояном. Даже если в какой-то конкретный момент не определяет, нет никакой гарантии, что поведение не придет с очередным обновлением баз.

Про каноникализацию - почитал стандарт и честно говоря - в шоке, в глубоком шоке. По стандарту каноникализировать может только полная реализация XML парсера. Потому что, как я понял 1) ссылки на пространства имен, на алгоритмы могут быть в виде Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411" и при каноникализации их нужно перевести в привычный Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"; 2) предписано отклонять ссылки на пространства имен содержащие относительные ссылки на этот же документ и не преобразовывать их в абсолютные; 3) объявления пространства имен сортируются вперед прочих атрибутов; 4) при этом атрибуты с указанием пространства имен (a:attr2, b:attr) сортируются не по указанному имени пространства (b), а по полной ссылке(http://www.w3.org/2001/XMLSchema), определенной для этого имени и только после этого по имени атрибута (attr); 5) одиночные теги преобразуются в пустые парные. Если 3 и 5 относительно несложно, реализовано почти во всех "самопальных" каноникализаторах, 2 тоже несложно, но не видел чтобы реализовали, то 1 и 4 - представляют реальный "гемор", если у нас не полноценный парсер. Конечно, можно предположить что сферическая в вакууме "наша ИС" такого XML не сгенерирует и забыть о них, но для массового распространения под разные ИС эти правила должны быть учтены.
Дополнительные правила применяются для переводов строк (реально в одном месте "намекается", что в каноникализированном тексте вообще не должно быть символов с кодами 10 и 13 - буду еще перечитывать когда до этого дойду), пробелов внутри самого тега , пробелов в содержимом тегов, комментариев.
Про кодировки наверно тоже очевидно - хотя большинства ГИС использует UTF-8, отдельные принимают-возвращают Windows-1251. Про 1С-овские кириллические теги вообще молчу.
Плюс к этому, если используется эксклюзивная каноникализация (обычно только для подписываемой части XML, но не для сертификата, xades или SignedInfo), нужно чтобы подписываемый элемент был "самодостаточным", то есть содержал все пространства имен, которые в нем используются, даже если уже объявлены в родительских тегах, но за исключение пространств имен указанных в потомках. Тут можно голову сломать, вычисляя куда же пространство вставить. В частности, если одно и то же пространство используется в 2 потомках, но не используется в самом теге и его родителях, то указано должно быть в каждом потомке отдельно. При этом у меня нашелся ответ от СМЭВ, в котором в подписываемом теге не указано даже его собственное пространство имен. rev
Более того, есть специальный Transform позволяющий указать, что после каноникализации, но перед вычислением хэша, нужно выполнить определенное XPath выражение. В качестве примера было приведено отбрасывание всего текста и подписывание только тегов.
Самое печальное тут то, что каждая конкретная ГИС может не поддерживать отдельные правила - то есть, если ориентироваться на все ГИС в одной библиотеке, то должны быть средства включения/отключения каждого определенного правила и некая "база знаний" что именно нужно отключать для какой ГИС. dash2

two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#6 Сообщение two_oceans » 02 мар 2017, 01:39

В продолжение темы поддержки длинных строк. Сделал все-таки гибридный тип - принцип примерно как у ansistring, но с дополнительными "фишками": хранятся актуальная длина данных строки, длина выделенной памяти, признак фиксирования и указатель на собственно выделенную память.
Плюсы: можно передавать в параметры (размер самого типа 16 байт, стек не забивает), размер автоматически увеличивается при необходимости (при этом выделяется новый буфер, данные копируются, старый буфер освобождается; максимальный размер для увеличения пока переехал из прошлой версии длинных строк - 20 МБ), размер можно задать вручную (правда если задать больше 20 МБ при обращении к памяти срабатывает исключение Range Check, вероятно надо будет увеличить описание типа-указателя до пары гигабайт).По умолчанию после данных еще и #0 записывается (но не считается в актуальную длину данных строки), то есть данные в буфере совместимы с PChar.

Гибридность проявляется в том, что можно установить признак фиксирования и при этом отключится изменение размера строки и "перепрыгивание" адреса буфера. При этом все "не вошедшие" в выделенную память символы будут отбрасываться как в обычном shortstring. Использовать можно по-разному: выделить 20 МБ, зафиксировать и использовать в вызовах WinApi, не опасаясь, что строка "перепрыгнет" на другой адрес после изменения одного символа как у ansistring. Или зафиксировать и вручную установить указатель на любой буфер, в том числе не динамический - так удобно хранить данные при вызовах WinApi, чтобы не объявлять кучу переменных.

Минус тоже выискался - у ansistring работа с указателем буфера прописана отдельно в исходнике компилятора, а у гибридного типа нет. Значит невозможно переопределить оператор присваивания одной гибридной строки другой - компилятор тупо копирует содержимое записи, в том числе затирая ссылку на буфер без освобождения буфера; вместо того чтобы скопировать данные в буфере в другой буфер.
Так что с присваиванием все также торчат уши в виде lstr_Assign. От присваиванию указателю правда избавился, но теперь желательно вызвать lstr_New для корректного выделения буфера, так как заранее не известно какой мусор попадет в начальное состояние переменной при размещении в стеке.
В общем избавился от фиксированных типов разной длины в пользу типа с переменной длиной - сразу стало легче писать. И с памятью должно быть экономичнее.

Функции связанные с криптоядрами попробовал объединить в отдельный класс, но пока непонятно что получается. Как выяснилось при запросе контекста КриптоПро выдает окно вставки носителя, буду еще разбираться что и как. Среди прочих материалов откопал исходник универсальной функции - в зависимости от параметров она возвращает хэш или подпись или сертификат или открытый ключ. Все в виде стримов. Для моего структурирования классов не подойдет, но вообще изящное решение. Понемногу выстраивается как использовать Криптопро и OpenSSL для аналогичных операций.

two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#7 Сообщение two_oceans » 28 июн 2017, 08:04

За прошедшие почти 4 месяца добавил пару проверок к гибридному типу и начал обдумывать стоит ли использовать его для экспортируемых функций библиотеки. Теоретически у типа-записи нет проблем как типа-объекта, но память выделенную внутри библиотеки (через менеджер памяти библиотеки) и возвращать нужно через тот же менеджер. С другой стороны, если выделить память извне, то и освобождать ее нужно извне. Таким образом, для входящих параметров экспортируемых функций гибридный тип можно использовать только в режиме фиксации, для исходящих параметров обязательно вызывать экспортируемую функцию освобождения. А значит режим фиксации теперь должен учитывать и это.
По криптооперациям: выбрал время набросал каркас xml для подписания. Заодно посмотрел проверку подписи... ну я вам скажу - подписать гораздо проще (не считая каноникализации, сляпал и готово), чем проверить. Для проверки же помимо расчета хэшей и подписи, надо извлечь сертификат из подписи (либо найти в своем хранилище), построить цепочку до корневого, проверить даты и неотозванность всех сертификатов в цепочке. Хотя конечно можно закэшировать результат проверки на какое-то время.
В случае майкрософтовского криптопровайдера - есть функция-комбайн, делающая все это, только похоже на xades-bes она не подойдет. С ручным построением цепочек пока "мрак", буду еще искать примеры. Извлечение сертификата из подписи тоже довольно своеобразное - похоже создается "временное" хранилище, даже если сертификат уже есть в системном хранилище. Таким же образом просматриваются сертификаты из файлов.

В случае OpenSSL проблема начинается с хранилища доверенных сертификатов.. по умолчанию, даже в версии под windows прописан путь в стиле linux, без буквы диска. Он теоретически конечно работает, но только если хранилище расположено на текущем диске и смотрится очень коряво. То есть windows автоматически дописывает текущий диск к пути и чтобы работать с папками на каждом диске, на каждом диске нужно будет хотя бы создать junction-ссылку на хранилище. К тому же определенно ожидаю проблемы с формированием автоматических символических ссылок на сертификаты, так как ссылки в разных операционных системах разные. А значит, добавление ручками. Конечно, можно указать хранилище и в командной строке для каждой команды. Попробовал (без создания прописанного хранилища) указать и подключиться к сайтам - все-таки нет, сертификаты сайтов не считает доверенными.

two_oceans
Ветеран
Сообщений: 546
Зарегистрирован: 30 сен 2016, 17:17
Благодарил (а): 439 раза
Поблагодарили: 415 раза

Поддержка длинных строк

#8 Сообщение two_oceans » 17 июл 2017, 08:08

За полмесяца у меня по теме прошел прогресс. Причина - наехали из структурного подразделения насчет подключения к ГИС ГМП. Служебная записка с объяснением что и как потянула на 5 страниц, скажу кратко: там тоже ЭЦП в SOAP. Причем не одна - запрос инкапсулируется в пакет СМЭВ, подписанный подписью ИС отправителя, при этом в запросе может быть до 100 начислений/платежей, каждый из которых подписывается тем, кто данные сформировал, не обязательно ИС отправителя - это могут быть структурные подразделения и подведомственные учреждения. При этом структурные подразделения не могут зарегистрировать ИС в СМЭВ, так что забота об ИС выпадает головной организации. Когда приходит к получателю на пакет еще наматываются ЭЦП всех узлов СМЭВ через которые пакет проходил.

В общем, с такой мотивацией я взялся за дело. На данный момент образовалось целых 3 слоя функций ниже объекта подписи: один хранит сертификаты независимо от криптопровайдера, второй определяет доступность и загружает определенный криптопровайдер (то есть независим от контейнера), третий выполняет собственно операции на определенном криптопровайдере (с указанным контейнером). По OpenSSL третьего слоя пока не доделал, зато вник в Майкрософтовский.

Весьма интересно спроектировано - hcryptprov как оказалось - не просто дескриптор криптопровайдера, а криптопровайдера с указанной ключевой "парой". Ключевая "пара" может быть пустой и запрос КриптоПро на выбор ключа не выводится, если указаны флаги NEW_KEYSET или VERIFY_CONTEXT, правда и подписать ими не получится. В первом случае ключевую пару нужно сгенерировать прежде чем подписывать, а второй используется для операций не связанных с закрытым ключом - вычисление хэша и проверка подписи. Для проверки подписи можно загрузить сертификат (например, считанный и перекодированный из проверяемой XMLDSIG) либо чисто данные ключа либо найти сертификат в хранилище (поиск пока не проверял) и получить дескриптор открытого ключа.

Насчет переворачивания байтов все подтвердилось: ms cryptoapi возвращает результат в little endian, а xmldsig-core стандарт предусматривает 2 типа ds:CryptoBinary и base64Binary. Оба определяются как big-endian bitstring, дополненная нулевыми битами слева до целого количества октетов (байтов). Обе кодируются в Base64. Отличие в том, что в ds:CryptoBinary байты слева, равные нулю, выкидываются перед кодированием, а в base64Binary остаются. Итого - в результате возвращенном ms cryptoapi нужно отзеркалить порядок байтов (не просто поменять endian по 2 или 4 байта как можно подумать, а самый первый из 32 байт с тридцать вторым, второй с тридцать первым и т.д.) перед кодированием в Base64, а при проверке отзеркаливать еще раз между декодированием Base64 и проверкой.

Еще интереснее выяснилось с .Net - там возвращается big-endian (лично не проверял), то есть для xmldsig переворот не нужен, просто кодирование/декодирование Base64. Однако, если пытаться проверить подпись .NET средствами ms cryptoapi (и наоборот) без переворота проверка провалится. То есть в подписи нет признака порядка байтов - если проверка выдала ошибку попробуйте перевернуть и проверить еще раз. :lol:
Дальше - больше, в целях защиты от подбора закрытого ключа при подписании каждый раз генерируется случайное число, то есть подписав 2 раза подряд одни и те же байты данных получите 2 разных последовательности байтов подписи. Таким образом, проверить работу алгоритма подписи сравнением не выйдет. Это отражается и на наборе функций - есть функция CryptSignHash, подписывающая (то есть шифрующая) вычисленный хэш с использованием закрытого ключа текущей ключевой пары и есть CryptVerifySignature проверяющая подпись по уже вычисленному хэшу и открытому ключу. В смысле проверяющая математическую корректность подписи, проверка сертификата не включена. Комбайн CryptSignMessage все же предназначен для cades. Еще пришлось переписать определение функции CryptStringToBinary, которая, по идее, может использоваться для декодирования Base64. Как выяснилось, зря время тратил, практически она настолько навороченная, что использовать не получается, об этом ниже.

Впечатлившись всем этим, написал слой обработки для ms cryptoapi и решил протестировать (первый раз с февраля) что получилось. Программа конечно выпала (один раз по range checking, второй по access denied), причину на 100% не установил, зато нашелся баг в модуле длинных строк - при освобождении запись передавалась по значению, а не по ссылке. Обычно мне это не мешало, так как освобождаю в конце функции и переменную больше не использую. А тут ловил выпад и обнаружил, что после освобождения в переменной длина ненулевая и остался адрес на освобожденную память.

Передал по ссылке, падать перестало и я получил от cryptoapi ошибку 0x80090017 (не найдено криптопровайдера нужного типа). Установил КриптоПро, получил хэш примера (который тест для плагина на сайтк криптопро) и удивился - не совпадает. Ладно, нашел примеры на саму функцию хэширования - не совпадает. Приглядевшись к передаче данных выяснилось, что хотя обычно строку надо передавать включая в длину нулевой символ (иначе тот самый access denied выпадает, когда winapi нулевой символ ищет), то в функцию хэширования передается длина без нулевого символа! При этом примеры хэша стали совпадать, но хэш примера с сайта - нет. Значит... каноникализация все же нужна нужна. Попытки привести вручную не удались. Сегодня поищу еще примеры, может быть найдется уже каноничный текст. Конечно, если функция хэширования на примерах работает нормально, то можно и без тестов попробовать все собрать в каноничный xml (ну то есть каноничными должны быть только части попадающие под хэш и подпись) и отправить на СМЭВ, но вот при получении и проверке подписи номер не пройдет - не факт, что придет каноничный xml. Так что, собственно сейчас занят поиском годной каноникализации без .Net и Java. Уже есть 3 варианта модулей: из комплекта компилятора, uXMLHelper, TclCanonicalizer. Тест покажет, годные ли. Из последнего уже пришлось выкинуть несколько строк, а типы дописать.

Кстати, касательно СМЭВ, похоже будет логичнее помимо тега Signature добавлять той же функцией и обертку тегом Security при определенном значении параметра формата. Свойства сертификата ГИС ЖКХ и метка времени узлов СМЭВ тогда тоже будут дополнениями.

Отправлено спустя 42 минуты 4 секунды:
Да, CryptStringToBinary оказалась суперкрутой функцией - в качестве параметра принимает тип кодирования строки, а на выходе должен быть массив байтов и действительный формат строки плюс количество символов не соответствовавших формату. Вот только проблема, что форматов функция "понимает" много и когда я передаю строку 44 байта в base64 ожидая получить 32 байта подписи... функция возвращает 62 ! байта, причем все они печатные символы - кажется, фактически вместо декодирования base64, функция кодирует их в base64 еще раз. Видимо не зря ее определение было косячным (и ссылка на импорт запорота): чтобы никто не использовал.

Ну ладно, в комплекте компилятора есть модуль кодирования, работающий с base64. Конечно прыгающая ansistring и короткая shortstring не лучший выбор, но на 32 байта казалось бы - вот оно! И снова нет, на этот раз декодирует прекрасно, но кодировать отказывается (причину пока не выяснял, возможно непечатные символы мешают). Ну тут выручает CryptBinaryToString, которая по-честному из массива байтов делает строку Base64. Вот так из 2 хромых модулей получилось нечто рабочее :D

Леший
Ветеран
Сообщений: 2957
Зарегистрирован: 06 окт 2015, 07:03
Благодарил (а): 1297 раза
Поблагодарили: 1816 раза

Поддержка длинных строк

#9 Сообщение Леший » 17 июл 2017, 09:03

Нихрена себе абракадабрв! И после этого чибисоиды пишут что системой может любой пользоваться, кто видел паровоз с калькулятором.

Sergey Cheban
Активист
Сообщений: 216
Зарегистрирован: 05 ноя 2016, 07:45
Благодарил (а): 74 раза
Поблагодарили: 113 раза

Поддержка длинных строк

#10 Сообщение Sergey Cheban » 17 июл 2017, 22:38

two_oceans писал(а):Однако, если пытаться проверить подпись .NET средствами ms cryptoapi (и наоборот) без переворота проверка провалится. То есть в подписи нет признака порядка байтов - если проверка выдала ошибку попробуйте перевернуть и проверить еще раз. :lol:

Вот не надо так делать. Надо - понимать, кто в каком формате данные получает и предоставляет, и переворачивать правильным образом. Это документировано:
The native cryptography API uses little-endian byte order while the .NET Framework API uses big-endian byte order. If you are verifying a signature generated by using a .NET Framework API, you must swap the order of signature bytes before calling the CryptVerifySignature function to verify the signature.

В сеть и в файлы - рекомендую выдавать данные в том виде, который использует .NET: это так называемый "сетевой порядок байтов", он традиционно используется для совместимости между машинами разных архитектур.

А в остальном - очень информативно.


Вернуться в «ГИС ЖКХ. Форум разработчиков программного обеспечения и всего, что с ним связано»

Кто сейчас на форуме

Количество пользователей, которые сейчас просматривают этот форум: нет зарегистрированных пользователей и 1 гость