RSS
Поддержка длинных строк
 
Тема больше эмоции, чем реально обсуждение. За ближайшие дни наметилась таки самая маленькая продвижка в сторону библиотеки подписания - вроде разобрался как получать хэш от КриптоПро через WinCryptApi. вдохновленный этим нашел кучу примеров подписей и решил что пора бы попроверять хотя бы хэши. Для этого нужно разобраться с длинными строками - обычных shortstring не хватает на закодированный сертификат. Попробовал встроенные типы - ansistring - вроде бы все замечательно, но при каждом изменении строка прыгает на другой адрес памяти, периодически (при обращении к символу внутри строки особенно) роняя всю программу (самое прикольное, что IDE тоже роняется). Да еще и не преобразуется в shortstring - это мне не понравилось. Как водится подумал - я напишу-ка свой модуль работы с длинными строками, с блек-джеком и ... ну, вы поняли.
Сказано - сделано. За основу взял старый модуль для строк длиной 260 символов, переименовал, переопределил размер. Но там были простые строки без длины и определения операторов. Определил дополнительно к бывшим новый тип как запись - в одном поле длина, в другом собственно массив символов нужной длины. Написал базовые процедуры вроде lstr_Assign/lstr_Concat (передача параметров-строк по ссылке естественно), разобрался с операторами (простое присваивание компилятор сам делает копированием памяти и не дает переопределить, зато можно присваивание между типами доопределить/поменять). Все так красиво написал, определил разные размеры строк (100Кб, 1Мб, 20 Мб), но смутило, что в примерах операторов параметры и результат не по ссылкам.
Выбрал оператор присваивания где только shortstring в параметрах и протестил. По результатам, захотелось долго материться. Действительно, компилятор попробовал протащить результат (строку 100 Кб) через стэк. Естественно это не удалось - на x86 стэк только 64 Кб (сами авторы компилятора пометили, что это из-за инструкции ret процессора и они не виноваты). Пока не нашел как помешать протаскиванию результата через стэк. Покрутил и так и этак - количество параметров и наличие результата у определенного оператора похоже зашито на уровне синтаксического транслятора. Пока не теряю надежду отыскать какую нибудь полезную директиву, если у кого было такое подскажите.
Аналогично нельзя объявлять такую переменную внутри функций - стек тоже переполняет. Объявления локальных shortstring пока оставил, но видимо и их надо поубирать - рано или поздно накроют стек.
Зато попутно выяснилось что можно на выход оператора выдать указатель. Но, этот указатель совершенно ничего "не знает" о переменной куда будет выдан (логично блин), поэтому под него надо выделить память внутри оператора присваивания. Внешне (в программе) это будет выглядеть как [code:2rkgwasi]type plstr100k=^longstr100k;
var p:plstr100k;
begin
p:='';
writeln(p.str);
Free(p);
end.[/code:2rkgwasi]Мда... вообще маразм строка присваивается указателю и это работает! вот как по такому догадаться, что при присваивании была выделена память и ее нужно освободить.
Полез в исходники как определено присваивание в ansistring... ОМГ, недостижимый уровень извращенства - тип прописан в исходниках компилятора, вместо присваивания всех длинных типов (строки, массивы, записи, объекты, вариант) выполняется процедура копирования памяти плюс индивидуально для каждого типа считаются ссылки на память и прочее. То есть чтобы ввести новый длинный тип "красиво" нужно добавить код типа в исходник компилятора, потом его везде учесть, потом откомпилировать свою новую версию компилятора. Что называется если начал - то делай по полному, а иначе будут торчать "уши" вроде lstr_Assign или Free.
Переписывать компилятор я конечно не стану - не такого блек-джека я хотел. Пока потестил такую версию, где можно присвоить строку указателю. Операторы поменял, чтобы входящие параметры по ссылкам, результат указателем. В общей сложности на все эти "исследования" ушли сутки. Работает, собака. Только надо не забыть потом везде освобождение памяти.
 
А я начал делать библиотеку подписания через OpenSSL.
Хэш считаю так:
[code:1mjl2t8v]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;[/code:1mjl2t8v]

Хэш сертификата можно один раз подсчитать через вызов в консоли:
[code:1mjl2t8v]function GetDigest(const Text: string): string;
begin
GetDosOutput('openssl dgst -binary -md_gost94 |openssl base64', Text, 30, Result);
Delete(Result, Length(Result), 1);
end;[/code:1mjl2t8v]
 
Благодарю. До OpenSSL у меня тоже в планах добраться, так что это пригодится. Пока еще не придумал как поудобнее хранить сертификат, чтобы подходил и к WinCryptoApi и к OpenSSL. Да, серьезно, где-то читал дескать WinCryptApi только с хранилища берет сертификаты и закрытые ключи. Однако после беглого просмотра документации похоже что это не совсем так и сертификат можно и из файла зацепить. Буду еще разбираться так ли все это.
Логично, что хэш сертификата понадобится вычислить один раз, но кроме того понадобятся и реквизиты сертификата - как минимум, серийный номер и издатель. Встает выбор хранить это где-то или каждый раз вытаскивать из сертификата. Случаев когда нужно сразу "мульён" запросов за раз подписать немного, значит либо реквизиты плюс хэш будут висеть в кэше в памяти/на диске, где их нужно будет как-то отличать (а вдруг мы на 1001 раз решили подписать другим сертификатом) либо 1001 раз его считать.
Еще один момент - надо посмотреть понадобится ли "переворачивать" хэш после OpenSSL (в смысле преобразовать Little Endian - Big Endian до кодирования в Base64). Если понадобится, то командная строка неверна.
По поводу вычисления в консоли - так конечно можно, но если потом устанавливать соединение к интернету этой же программой для отправки подписанного, то увы есть шанс, что антивирусы заклеймят трояном. В этом смысле из скриптов vbscript, php, perl или python вызывать консоль проще - их антивирус проверяет по другому.

Ну и отчитаюсь по длинным строкам - написал базовые обобщения операций, теперь наделать новых типов разной длины стало проще. Также уже могу удалить тег подписи из XML и вытащить значение хэша из подписанного файла. Все длинными строками. Пока меня отвлекли на другую работу, так что дело идет медленно.
 
Предполагаю один раз взять данные сертификата, записать в инифайл и если нужно беру их оттуда
Серийник берется просто:
Цитата
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 секунды:
Забыл еще, так загружаю сертификат
[code:2jlddosl]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;
[/code:2jlddosl]

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

Отправлено спустя 2 минуты 53 секунды:
В консоль обращаюсь через StdIn и StdOut, у меня на машине лиценз. DrWeb не обращает внимания
 
Благодарю. Инифайл это как бы сказать.. наводит мысль о временах 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
 
В продолжение темы поддержки длинных строк. Сделал все-таки гибридный тип - принцип примерно как у ansistring, но с дополнительными "фишками": хранятся актуальная длина данных строки, длина выделенной памяти, признак фиксирования и указатель на собственно выделенную память.
Плюсы: можно передавать в параметры (размер самого типа 16 байт, стек не забивает), размер автоматически увеличивается при необходимости (при этом выделяется новый буфер, данные копируются, старый буфер освобождается; максимальный размер для увеличения пока переехал из прошлой версии длинных строк - 20 МБ), размер можно задать вручную (правда если задать больше 20 МБ при обращении к памяти срабатывает исключение Range Check, вероятно надо будет увеличить описание типа-указателя до пары гигабайт).По умолчанию после данных еще и #0 записывается (но не считается в актуальную длину данных строки), то есть данные в буфере совместимы с PChar.

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

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

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

В случае OpenSSL проблема начинается с хранилища доверенных сертификатов.. по умолчанию, даже в версии под windows прописан путь в стиле linux, без буквы диска. Он теоретически конечно работает, но только если хранилище расположено на текущем диске и смотрится очень коряво. То есть windows автоматически дописывает текущий диск к пути и чтобы работать с папками на каждом диске, на каждом диске нужно будет хотя бы создать junction-ссылку на хранилище. К тому же определенно ожидаю проблемы с формированием автоматических символических ссылок на сертификаты, так как ссылки в разных операционных системах разные. А значит, добавление ручками. Конечно, можно указать хранилище и в командной строке для каждой команды. Попробовал (без создания прописанного хранилища) указать и подключиться к сайтам - все-таки нет, сертификаты сайтов не считает доверенными.
 
За полмесяца у меня по теме прошел прогресс. Причина - наехали из структурного подразделения насчет подключения к ГИС ГМП. Служебная записка с объяснением что и как потянула на 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
 
Нихрена себе абракадабрв! И после этого чибисоиды пишут что системой может любой пользоваться, кто видел паровоз с калькулятором.
 
Цитата
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: это так называемый "сетевой порядок байтов", он традиционно используется для совместимости между машинами разных архитектур.

А в остальном - очень информативно.
 
Цитата
Sergey Cheban пишет:
Вот не надо так делать. Надо - понимать, кто в каком формате данные получает и предоставляет, и переворачивать правильным образом.
Ну, потому и стоит смайлик, что "вредный совет". Про четко прописанные форматы и речи как бы нет, я не говорю, что после переворота и второй проверки подпись следует принять, но вывод ошибки "Похоже использован неверный endian, используйте big endian, определенный по стандарту" был бы более информативен для начинающих разбираться в теме (и копирующих примеры, не понимая их), чем просто "Плохая подпись".

Хотя, например, не понятно почему XMLDSIG, в котором почти все указывается параметрами (хотя для некоторых допустимое значение определено только одно! как для кодировки - Base64) не требует указывать big-endian явно в DigestValue и SignatureValue, а жестко определяет порядок байтов в базовом типе. Это бы тоже натолкнуло копирующих примеры (без чтения правильного, но мозгодробительного стандарта) на правильные мысли. Сетевой порядок байтов - это конечно хорошо, но фактически под win32 на intel x86-x64 программисты сталкиваются с ним главным образом при кодировании ip-адреса (и там он воспринимается скорее как "плагиат реализации IP протокола с линуха"). И наверно все, навскидку не припомню других широко распространенных применений.
А вот про "самописные" форматы, которыми славятся российские программы, вообще мрак.

В конце концов, в случае ГОСТ еще есть разные парамсеты и это уже обрабатывается криптопровайдером - сертифицированный криптопровайдер считывает парамсет из сертификата, то есть выдаст ошибку даже если подпись верна для другого парамсета (про несертифицированный такой гарантии нет, например, он может принимать только один парамсет, остальные отклонять или перебирать все "до победного", что неправильно). А вот если загружать "голый" открытый ключ, то вопрос парамсета встает в полный рост, так как нет сертификата из которого парамсет можно считать и его придется указывать отдельно. То есть опять же возникает вопрос "перебора".

Еще момент, который тоже хочется "перебрать" - возможность нахождения в контейнере 2 пар ключей: одна - типа AT_SIGNATURE, другая типа AT_KEYEXCHANGE. Формально первый тип предназначен для подписи, второй для согласования ключей. cryptoapi требует их указания и выдает ошибку, если такого типа в контейнере нет. КриптоПро их тоже различает и позволяет засунуть в один контейнер. Вот только: 1) могу ошибаться, но криптофункциям ГОСТ похоже все равно на тип ключа, подписывает ключом AT_KEYEXCHANGE без проблем; 2) пока не встречал контейнера КриптоПро c 2 ключами или контейнера с AT_SIGNATURE. Из объяснения (ссылка ниже) смутно понял, что AT_SIGNATURE имеет ряд ограничений использования в приложениях Майкрософт. С другой стороны, прописывать в программе AT_KEYEXCHANGE неправильно, вдруг встретится контейнер с AT_SIGNATURE. Пока указал AT_KEYEXCHANGE, но раздумываю может быть, помимо указания только одного типа, ввести ли для библиотеки значения вроде KEYEXCHANGE_FIRST и SIGNATURE_FIRST, позволяющие указывать предпочитаемый и при этом перебирать ключ второго типа, если предпочитаемый не нашелся. Коллеги, какие мысли на этот счет?
Цитата
... US crypto exporting restrictions were only regarding strength of encryption keys but not signature keys. Microsoft was required to provide prove to the government that they never allow signature keys to be used for encryption purposes before they were allowed to ship Microsoft encryption modules with Windows. ... This all is explanation of why you get keyusage restrictions with Microsoft CSP, but not with thirdparty CSP.Ссылка
Похоже история тянется со времен, когда были ограничения экспортной криптографии в США и с российскими криптопровайдерами не связана.

Открываю сертификат через ASN.1 Editor, в нем есть ветка с ключом, для нее указан OID 1.2.643.2.2.19 (ГОСТ Р 34.10-2001, алгоритм подписи, открытый ключ), параметры: 1.2.643.2.2.36.0 (ГОСТ Р 34.10-2001, алгоритм подписи, параметры обмена по умолчанию), 1.2.643.2.2.30.1 (ГОСТ Р 34.11-94, хэш функция, параметры по умолчанию). Алгоритм хэша ГОСТ Р 34.11-94 (OID 1.2.643.2.2.9), в сертификате не виден. В целом алгоритм хэша/подписи ГОСТ Р 34.11-94/34.10-2001 имеет OID 1.2.643.2.2.3, указан в самом начале сертификата.
Как я понимаю, 1.2.643.2.2.36.0 - это как раз парамсет (в данном случае exA) и по слову "обмена" похоже цифра 36 помимо прочего как раз указывает, что это ключ обмена, то есть AT_KEYEXCHANGE. В примере настроек OpenSSL парамсет указан как A, по аналогии - это AT_SIGNATURE. Выходит если в программе жестко прописать AT_KEYEXCHANGE, то будет беда с ключами сгенерированными в OpenSSL? Попробовать считывать из сертификата парамсет и по нему определяться какой ключ?

Не разобрался еще вот с чем - получаю hcryptprov с пустым ключом и флагом VERIFY_CONTEXT и создаю хэш, запрашиваю CryptGetHashParam с кодом HP_OID (0xA) и получаю "1.2.643.2.2.30.1" и по параметрам хэша КриптоПро контрольные значения совпадают. Тут и тут утверждается, что если возвращает "1.2.643.2.2.30.1" то все вообще окей, а иначе надо устанавливать при проверке хэша/подписи то же значение параметров, что было при вычислении хэша/подписи. На форуме КриптоПро то же самое пишут.
Вопрос: а что указывать, если в сертификате окажется другое значение параметров хэша.. значение из сертификата только на проверку самого сертификата распространяется или на все операции с сертификатом? Ну в смысле вычисление хэша конечно идет без участия ключей и сертификата, но если хэш потом нужно подписать и приложить сертификат, то не будут ли параметры хэша для проверки считаны из сертификата? Значит их наверно нужно синхронизировать с сертификатом при вычислении подписи и проверке подписи. А что с другими хэшами в XMLDSIG - тоже согласовывать по сертификату? Итого: похоже все работают на параметрах по умолчанию и если их изменить вылезет масса недоработок в различном ПО, входящем в цепочку передачи данных.
 
Цитата
two_oceans пишет:
но вывод ошибки "Похоже использован неверный endian, используйте big endian, определенный по стандарту" был бы более информативен для начинающих разбираться в теме (и копирующих примеры, не понимая их), чем просто "Плохая подпись".
У тех, кто думает о безопасности, свои профессиональные привычки. Они стараются не передавать ничего лишнего, потому что "всё, что вы скажете, может быть использовано против вас".

Цитата
two_oceans пишет:
Хотя, например, не понятно почему XMLDSIG, в котором почти все указывается параметрами (хотя для некоторых допустимое значение определено только одно! как для кодировки - Base64) не требует указывать big-endian явно в DigestValue и SignatureValue, а жестко определяет порядок байтов в базовом типе.
Ну... Формат такой. Посмотрите на число 1234. Это и для нас тысяча двести тридцать четыре, и для евреев, которые пишут справа налево. Как-то обходимся без явного указания, в какую сторону число надо читать.

Цитата
two_oceans пишет:
Сетевой порядок байтов - это конечно хорошо, но фактически под win32 на intel x86-x64 программисты сталкиваются с ним главным образом при кодировании ip-адреса (и там он воспринимается скорее как "плагиат реализации IP протокола с линуха"). И наверно все, навскидку не припомню других широко распространенных применений.
Навскидку - посмотрите на byte order mark, позволяющий отличить little-endian utf-16 от big-endian utf-16.
Но вообще byte order - это большой головняк. Представьте себе крупную компанию, в которой исторически используется big-endian железо, которое пишет данные на диски в своём родном формате. А теперь представьте, что руководство этой компании интересуется: "А не перейти ли нам с этого железа на обычные x86? Перекомпилировать исходники под другую платформу, и подключить к ней имеющиеся диски". И вот тут выясняется, что перекомпилировать мало, надо ещё везде расставить забытые при разработке ntohs и htons, чтобы работало.

Цитата
two_oceans пишет:
Еще момент, который тоже хочется "перебрать" - возможность нахождения в контейнере 2 пар ключей: одна - типа AT_SIGNATURE, другая типа AT_KEYEXCHANGE. Формально первый тип предназначен для подписи, второй для согласования ключей.
Я не в теме, но гугл подсказал, что не совсем так:
http://cpdn.cryptopro.ru/content/csp40/ ... 017bf.html
Цитата
AT_KEYEXCHANGE - "Предназначена для обмена сессионными ключами и ЭЦП", AT_SIGNATURE - "Предназначена для ЭЦП".

Цитата
two_oceans пишет:
Тут и тут утверждается, что если возвращает "1.2.643.2.2.30.1" то все вообще окей, а иначе надо устанавливать при проверке хэша/подписи то же значение параметров, что было при вычислении хэша/подписи. На форуме КриптоПро то же самое пишут.
Вопрос: а что указывать, если в сертификате окажется другое значение параметров хэша.
Я опять же не в теме, но сдаётся мне, что правильно будет отказаться принимать такой сертификат от греха подальше: если мы будем принимать разные параметры криптоалгоритма, то злоумышленник сможет предложить нам наименее криптостойкие параметры.
Вообще, фраза "Сообщество российских разработчиков СКЗИ согласовало используемые в Интернет параметры ГОСТ Р 34.11-94, см. RFC 4357" (отсюда) вызывает у меня недоумение. Но - имеем что имеем.

И ещё: https://www.cryptopro.ru/news/2017/06/o ... lya-2017-g
Цитата
Обращаем внимание, что в связи с требованиями ФСБ России, сопряженными с запретом использования ГОСТ Р 34.10-2001 для формирования подписи после 1 января 2019 года (источник), с 1 июля 2017 года в КриптоПро CSP версий 3.9 и 4.0, а также КриптоПро JCP 2.0 будут появляться предупреждения о необходимости скорого перехода на ГОСТ Р 34.10-2012 при формировании ключей ГОСТ Р 34.10-2001, а с 1 октября 2017 года – и при формировании подписи по ГОСТ Р 34.10-2001.
Я так понимаю, что "1.2.643.2.2.30.1" - это старый гост.
 
Цитата
Sergey Cheban пишет:
У тех, кто думает о безопасности, свои профессиональные привычки. Они стараются не передавать ничего лишнего, потому что "всё, что вы скажете, может быть использовано против вас".
Логично, доходит до того, что вводят специальную задержку, чтобы по времени обработки нельзя было определить на каком этапе ошибка по уменьшению длительности. Но это - внутри одной операции проверки, я не понимаю, как добавление второй целой операции (то есть тоже выровненной по длительности), срабатывающей при неправильном ответе, может создать дополни-тельную угрозу. И тем более как ее может создать изменение ответа на один из 2^256 вариантов хэша. В принципе, понятно, что безопасность и комфорт не очень совместимые понятие.
Цитата
Sergey Cheban пишет:
Навскидку - посмотрите на byte order mark, позволяющий отличить little-endian utf-16 от big-endian utf-16.
В скомпилированных программах конечно utf-16 широко используется, но в веб (и соответственно БД для веб) большая часть Юникода - utf-8. И в utf-8 byte order mark скорее сродни чуме чем спасению - большинство программ (включая браузеры) впадают в различные баги, встретив byte order mark в середине файла, а некоторые и в начале. Поэтому концепт создания страницы из кусочков приводит к тому что надо из всех кусочков byte order mark убрать, указав кодировку другими средствами.
Цитата
Sergey Cheban пишет:
big-endian железо, которое пишет данные на диски в своём родном формате
Мысль уловил, головняк изрядный, хотя пример про железо наверно не очень удачный, так как данные пишут диски, но и читают тоже они. Те же самые big-endian жесткие диски подключенные к одной платформе и к другой платформе прочитают то же самое, другой вопрос, что драйвер, работающий с ними должен будет данные перевернуть перед передачей прикладным программам. В зависимости от разных факторов, часть данных возможно не придется преобразовывать в программе после этого, но преобразование других данных может еще больше запутаться. Кстати, и под x86 некоторые устройства (вроде бы даже USB, внезапно) работают в big-endian, но мы этого не замечаем, потому что драйверы справляются. С оптическими дисками наверно хуже, не знаю есть ли там byte order mark.
Цитата
Sergey Cheban пишет:
Я не в теме, но гугл подсказал, что не совсем так:
Спасибо за ссылку, почитаю. На функции генерации как-то не обратил внимание, тем более что это не "майкрософтовская" функция, а именно "криптопрошная", которую "майкрософтовская" вызывает внутри после нескольких промежуточных шагов.
Цитата
Sergey Cheban пишет:
Я опять же не в теме, но сдаётся мне, что правильно будет отказаться принимать такой сертификат от греха подальше: если мы будем принимать разные параметры криптоалгоритма, то злоумышленник сможет предложить нам наименее криптостойкие параметры.
Логично, но как-то неправильно. Российские УЦ не сообщают клиентам практически никакую техническую информацию о будущем сертификате. Пару недель назад добивался от сотрудников регионального отдела продаж одного из крупнейших УЦ - добился только что в выбранный сертификат для ЭП-ОВ не будет включена персональная информация и что в других ИС кроме СМЭВ он вроде бы не должен работать (как мне и надо). Внятно объяснить какие именно расширения сертификата и значения расширенного использования будут в сертификате они не могут, максимум назвать тарифный план и спросить у кого-то будет ли работать на определенной ИС. Меня три раза переспросили действительно ли я хочу чтобы не работало. Про параметры хэша, полагаю, они даже не слышали.

Итого, покупая сертификат и платя денежки, клиенты УЦ покупают "кота в мешке". Шанс, что кто-то намеренно поменяет параметры хэша в сертификате хоть и не нулевой, но и для рядового клиента не велик. Зато велик шанс, что кто-то не зная ничего получит сертификат с нестандартными параметрами хэша и если мы его отклоним - попадет на деньги, так как придется заказывать сертификат в другом УЦ (в том же УЦ выдадут с теми же параметрами скорее всего). Нехорошо как-то. УЦ молодцы, в заявке на изготовления сертификата включен пункт, что клиент соглашается, что УЦ не несет ответственность за невозможность использования сертификата в определенной ИС.
Цитата
Sergey Cheban пишет:
Я так понимаю, что "1.2.643.2.2.30.1" - это старый гост.
Не вдавался настолько в тему про новый ГОСТ, с текущим бы разобраться, но попадалась на глаза статья про будущий стандарт. Во-первых, разработчики стандартов всех уже запутали с 34.10 (вроде бы алгоритм шифрования?), 34.11 (алгоритм хэша) и 34.10/34.11 (алгоритм подписи, из которого умудряются тоже сократить до 34.10). Во-вторых, ГОСТ то может и новый, но в его рамках переопределены большинство алгоритмов из старого (названы "Магма", все те же с 1989 года) и добавлен новый "Кузнечик" (34.12-2015). Хэш тоже добавился новый "Стрибог" (34.11?-2012), с длиной 256 для "Магмы" и 512 для "Кузнечика". А еще режимы блочных шифров определили в 34.13-2015.
Путаница изрядная, ясно только, что с 2019 года нужно обновить криптопровайдер на поддерживающий новый хэш и видимо сгенерить новые ключи (с учетом что ключи как правило на год, ничего существенно нового - и там постоянно меняем). В итоге, надо будет сесть и хорошенько разобраться, что именно они понимают под схемой подписи 34.10-2001 (видимо ГОСТ Р 34.11-94/34.10-2001) и чем отличается от нового ГОСТ 2012 года. Судя по очередности принятия стандартов, исключительно хэшем. Пользователей OpenSSL полагаю тоже очень обрадует в 2019 году, если новый ГОСТ не будет там поддерживаться к тому времени (не изучал вопрос поддерживается ли сейчас). Кстати, не новый ли ГОСТ требует ГИС ЖКХ, когда ругается, что нет согласованного ФСБ алгоритма.

Про текущую поддержку новых ключей ничего утешительного сказать не могу - буквально вчера пытался установить сертификат с 2012 в алгоритме на компьютер, где криптопро 4.0 и континент-АП 3.7... вот я Вам скажу "засада" - открываю сертификат из оснастки сертификаты все нормально, открываю тот же сертификат с рабочего стола: "Произошла ошибка при проверке отношений сертификатов", "Сертификат содержит неверную подпись". Как я понял, континент теперь тоже содержит свой криптопровайдер и криптопро (выставленный "по умолчанию") не может проверить сертификат, выпущенный континентом. А из хранилища сертификатов, где проставлена ссылка на контейнер, открывается криптопровайдер континента и все нормально. Причем в криптопро основной алгоритм 2001 года (логично, что 2012 не понимает. попробовал переключить на 2012, но отличий не заметил), а континентовский криптопровайдер не виден в списке (значит тип не такой, как у криптопро, но реализует те же алгоритмы).
Еще смешнее - похоже они поставлены в комплекте, так как для прошлого континента требовался криптопро, а компьютер сертифицированный и удалить криптопро нельзя. Конечно континент работает, проблема не критична, но конфликт - нехороший признак.

Отправлено спустя 7 минуты 3 секунды:
Еще статья
 
Цитата
two_oceans пишет:
Мысль уловил, головняк изрядный, хотя пример про железо наверно не очень удачный, так как данные пишут диски, но и читают тоже они. Те же самые big-endian жесткие диски подключенные к одной платформе и к другой платформе прочитают то же самое, другой вопрос, что драйвер, работающий с ними должен будет данные перевернуть перед передачей прикладным программам.
Пример взят из жизни. Решать такие вещи на уровне драйвера нельзя: драйвер ведь не знает, что он читает, int или строку. Строку переворачивать нельзя, чтобы из abcdef не получилось badcfe. В результате руководство стоит перед выбором: то ли остаться на старом железе (а оно экзотическое, дорогое, не дружит с современными средствами разработки и пр.), то ли внимательно расставить ntohl/htons по всему коду (при этом кода много, цена ошибки велика, да ещё и производительность может пострадать).

Цитата
two_oceans пишет:
Логично, но как-то неправильно. Российские УЦ не сообщают клиентам практически никакую техническую информацию о будущем сертификате. Пару недель назад добивался от сотрудников регионального отдела продаж одного из крупнейших УЦ - добился только что в выбранный сертификат для ЭП-ОВ не будет включена персональная информация и что в других ИС кроме СМЭВ он вроде бы не должен работать (как мне и надо). Внятно объяснить какие именно расширения сертификата и значения расширенного использования будут в сертификате они не могут, максимум назвать тарифный план и спросить у кого-то будет ли работать на определенной ИС. Меня три раза переспросили действительно ли я хочу чтобы не работало. Про параметры хэша, полагаю, они даже не слышали.
Увы.

Цитата
two_oceans пишет:
Не вдавался настолько в тему про новый ГОСТ, с текущим бы разобраться,
Я тоже не в теме различий между ними, но подпись, сформированная по новому госту, может прийти от ГИС в самый неожиданный момент.

Цитата
two_oceans пишет:
Путаница изрядная, ясно только, что с 2019 года нужно обновить криптопровайдер на поддерживающий новый хэш и видимо сгенерить новые ключи (с учетом что ключи как правило на год, ничего существенно нового - и там постоянно меняем).
Криптопровайдер надо обновлять уже сейчас (я так понимаю, что нужен криптопро 4.0). И готовиться к приёму подписи по новому госту - тоже сейчас, такая подпись в любой момент может прийти (примерно дату можно прикинуть, посмотрев на дату истечения сертификата ГИС). А ключи - да, должны в рабочем порядке обновиться, на это и дано время с 2013 до 2019 года.

Цитата
two_oceans пишет:
Кстати, не новый ли ГОСТ требует ГИС ЖКХ, когда ругается, что нет согласованного ФСБ алгоритма.
Сейчас, по идее, старый гост пока должен работать (как на самом деле, не знаю). Я бы предположил, что ГИС требует хоть какой-нибудь ГОСТ, но именно ГОСТ, а не американские алгоритмы. По умолчанию ГОСТа нет ни в windows, ни в браузерах.
 
Цитата
Sergey Cheban пишет:
int или строку.
В этом смысле да, драйвер скорее может порядок посылки по времени в протоколе устройства поменять (что и делается в USB).
Цитата
Sergey Cheban пишет:
внимательно расставить ntohl/htons по всему коду (при этом кода много, цена ошибки велика, да ещё и производительность может пострадать
Как мне кажется, если уже есть перекомпилированный рабочий код на другую платформу, то весь вопрос в разовом перевороте данных на диске (с учетом длины переворачиваемых частей). То есть выяснить местоположение и размер каждой части и перевернуть. Усложнять весь код из-за необходимости разовой операции как-то черезмерно рискованно. А при разовой операции, какая бы она не была долгая, про урон производительность речи не идет.
Цитата
Sergey Cheban пишет:
Криптопровайдер надо обновлять уже сейчас (я так понимаю, что нужен криптопро 4.0).
Ну готовиться-то конечно можно, вот только новая версия криптопровайдера это еще и новые константы во всех функциях, морока еще та. Судя по прошлым версиям, КриптоПро никак не может протащить одно название и значение константы в следующую версию. Конечно за год ничего не улучшится, но уже будет некий гайд от тех кто наткнулся на подводные камни.

Меня вот озаботило другое - я думал делать внутренний УЦ на ГОСТ-2001 и корневой сертификат до 2040 года бахнуть (подчиненный УЦ лет на 5 и сертификаты на ПК (машинные) года на 3). Но если ГОСТ-2001 придется менять через полтора года, то наверно надо сразу ГОСТ-2012 закладывать. Вот печаль-то.
Цитата
Sergey Cheban пишет:
Сейчас, по идее, старый гост пока должен работать (как на самом деле, не знаю). Я бы предположил, что ГИС требует хоть какой-нибудь ГОСТ, но именно ГОСТ, а не американские алгоритмы. По умолчанию ГОСТа нет ни в windows, ни в браузерах.
Все так, проблема в том, что все сделано по инструкции: установлен КриптоПро 3.6, cades plugin и настроены доверенные узлы в IE - сайт налоговой говорит ГОСТ-2001 есть, тестирует и переключается на него в HTTPS, при этом ГИС ЖКХ говорит "ГОСТа нет" в том же браузере. И соединение с помощью OpenSSL на сайт ГИС ЖКХ с шифросьютом гост-2001 тоже не проходит, выбирается американский алгоритм. Потому у меня и возникли подозрения, те кто говорил, что сообщение не выдает присылали скриншоты с Windows 8.1 или 10, на которых нужна новая версия КриптоПро (которая попутно поддерживает новые шифры).
 
Цитата
two_oceans пишет:
Как мне кажется, если уже есть перекомпилированный рабочий код на другую платформу, то весь вопрос в разовом перевороте данных на диске (с учетом длины переворачиваемых частей). То есть выяснить местоположение и размер каждой части и перевернуть. Усложнять весь код из-за необходимости разовой операции как-то черезмерно рискованно. А при разовой операции, какая бы она не была долгая, про урон производительность речи не идет.
Разовая переконвертация - скорее всего, не вариант.
- Это не ГИС и не Росреестр, которые могут себе позволить выключиться на пару месяцев, чтобы сменить форматы данных. Money never sleep.
- Данных много. Устройства, на которых они хранятся, подключены не к какой-то конкретной машине, а к сети.
- Заменить зоопарк машин, работающих с этими данными, за один приём не получится. И по техническим причинам (там десятки тонн компьютеров), и по организационным. Никто не будет рисковать, заменяя все компьютеры одновременно.
Сначала в сеть добавят одну little-endian машину и убедятся, что она _читает_ данные нормально. Потом вторую. Потом заведут какое-нибудь тестовое хранилище, в которое little-endian машины смогут писать, и проверят, что big-endian смогут это читать. Потом переведут в это хранилище какие-нибудь не очень важные данные и проверят, что клиенты не жалуются. Потом, постепенно, будут доверять little-endian машинам всё более важные операции. И только потом - начнут потихоньку выключать big endian. Как-то так (в очень упрощённом изложении и не затрагивая взаимодействие этого процесса с другими событиями).

Цитата
two_oceans пишет:
Меня вот озаботило другое - я думал делать внутренний УЦ на ГОСТ-2001 и корневой сертификат до 2040 года бахнуть (подчиненный УЦ лет на 5 и сертификаты на ПК (машинные) года на 3). Но если ГОСТ-2001 придется менять через полтора года, то наверно надо сразу ГОСТ-2012 закладывать. Вот печаль-то.
Внутренний - беспроблемнее на RSA делать, imho. По крайней мере, там криптопровайдеры во всех ОС из коробки доступны и бесплатны. А если вдруг RSA сломают, то внутренний УЦ будет не самой большой проблемой. Но если хочется гост, то, конечно, "безумству храбрых поём мы песню".

Цитата
two_oceans пишет:
Все так, проблема в том, что все сделано по инструкции: установлен КриптоПро 3.6,
Ну вот с 2019 года криптопро 3.6 превратится в тыкву. Поэтому я бы предложил, добившись хоть какой-то работоспособности на 3.6, не затягивать и как можно скорее попробовать 4.0. Получится - хорошо. Не получится - отправить запрос в техподдержку и жить на 3.6, пока не ответят.
Второй вариант - не пробовать, а просто спросить у ГИС, как и когда она собирается переходить на новый гост, и не пора ли обновить инструкцию (она, небось, с выхода 3.6 ни разу не обновлялась).

PS. Вообще, это позор, что гост появился в 2012 году, а его реализация - только в 2016. И второй позор - отсутствие бесплатной реализации и прочие vendor lock'и. Но - "это не беда, это расходы".
 
Цитата
Sergey Cheban пишет:
Как-то так (в очень упрощённом изложении и не затрагивая взаимодействие этого процесса с другими событиями).
Ясно.. впрочем, разово не значит одновременно и все выключить. С учетом сказанного, а если так: переключить один жесткий диск на только чтение, скопировать, перевернуть данные на копии, подключить копию к новому хранилищу для чтения, протестить, при успехе перенести процессы связанные с данными на том диске на little endian компьютер (то есть создание процесса на LE, тест процесса, запрет процесса на BE), включить полный доступ на копии, исходный диск отключить (до конца переноса всех дисков), скопировать следующий диск и т.д. При ошибке - откат шага (запретить процесс на LE, отменить запрет на процесс BE, полный доступ на исходный диск), проанализировать что "не так" и повторить с учетом исправлений. Если процессы включают резервирование, то проблем с переключением одного диска на чтение вообще не должно быть, но будет проблема с дифференциальным копированием и его переворотом. Однако "разницу" обработать еще быстрее чем целый диск, так что это тоже решаемо.
В этом случае тоже потребуется некое ПО для копирования и правильного переворота, но его можно сделать сильно упрощенным по сравнению с бизнес-ПО для каждого endian, так как переворот можно заранее отладить по каждому типу файлов.
Цитата
Sergey Cheban пишет:
Внутренний - беспроблемнее на RSA делать, imho. По крайней мере, там криптопровайдеры во всех ОС из коробки доступны и бесплатны. ... Но если хочется гост, то, конечно, "безумству храбрых поём мы песню".
На американских алгоритмах уже есть внутренний УЦ (Майкрософтовский) в домене. При установке компонента УЦ можно было выбрать ГОСТ (КриптоПро 3.6 на сервере тоже есть), но вроде бы варианты взаимоисключающие. Там тоже свои заморочки - ставить IIS для веб-интерфейса я не хочу (много дыр), а из оснастки сертификаты можно только фиксированные типы сертификатов ("профили") запрашивать, несоответствующие профилю расширения можно заполнить, но их срезает при выпуске сертификата. Например, в сертификат для сервера терминалов я хотел включить и доменное имя и IP-адрес, но IP срезался. Теперь если зайти по IP, то предупреждает "сертификат не соответствует имени узла". Как добавлять профили - не разбирался. Мне наверно проще будет сделать подчиненный УЦ в OpenSSL, приладить к нему интерфейс HTA и издавать там, чем разбираться в надуманных ограничениях УЦ Майкрософта.

По ГОСТ: проблема даже не в том, что "хочется" ГОСТ - персональные данные передающиеся из одного подразделения в другое подразделение по общей сети провайдера должны быть защищены ГОСТ. Хотя сеть провайдера практически как локальная, но это не отменяет необходимость ГОСТ. Сейчас сетевая связность настроена, как обычная VPN (средствами Windows, а VPN нужна для обхода ограничений Windows по маске) и с этим 2 проблемы: 1) шифр не ГОСТ, ключ смешной длины. Для более надежного нужно серьезно разбираться с политиками VPN; 2) VPN соединение нужно устанавливать вручную. Конечно, можно настроить различные сценарии автозапуска, только со всеми одна проблема - допустим, соединение установили, потом я перезагрузил сервер, сервер считает соединения разорванными, но на клиенте соединение все еще считается подключенным! Чтобы работало его приходится вручную разъединить и подключить снова (ну или перезагрузиться).
Есть всем известное решение - Vipnet клиент, вот только там: 1) много лишнего, в данном случае нет необходимости шифровать ВСЕ соединения, фильтровать посторонние; постоянно запрашивает пароль на контейнер при входе, компьютер существенно тормозит; 2) клиент должен подключаться к координатору либо нужно по координатору в каждом подразделении + если сеть своя нужен комплекс администратора, в итоге выходит дорого; 3) конфликтует с КриптоПро - можно разрулить, но работать будет один криптопровайдер. Есть какое-то VPN решение от КриптоПро, подробности не узнавал, но тоже тянет на круглую сумму.

Для ГОСТ-2001 есть реализация stunnel (на основе исходников OpenSSL), то есть для внутреннего использования подойдет, дело только в сертификатах. Более того есть версия stunnel работающая с КриптоПро (так уж получается, что на большинстве ПК с персональными данными КриптоПро уже установлен), есть библиотека позволяющая из OpenSSL использовать сам КриптоПро (к слову, позволяет настроить веб-сервер с 2 сертификатами: один для ГОСТ с поддержкой TLS 1.0, другой обычный американского алгоритма с поддержкой TLS 1.2). Такая библиотека выручит, если вдруг кто скажет, что конкретная сборка OpenSSL не сертифицирована. Как я понимаю, туннель устанавливается для каждого входящего отдельно, то есть вручную переподключать тоже не надо. Для конкретной задачи - идеально.
Естественно для внутреннего использования сертификаты не стоят всех заморочек/денег получения в аккредитованном УЦ, да и российские УЦ все больше на ФЛ, ИП и ЮЛ ориентируются, внести дополнительное имя для сертификата узла или наименование ИС целая проблема. Поэтому все сводится к еще одному внутреннему УЦ с ГОСТ на OpenSSL, где я смогу рулить составом сертификата как пожелаю.

С ГОСТ-2012 будет проблема при отсутствии реализации в OpenSSL. Хотя и тут есть шанс попробовать библиотеку связывающую OpenSSL и КриптоПро, а вдруг она сумеет из КриптоПро 4.0 ГОСТ-2012 подключить (помечтать не вредно, но с учетом, что библиотека до сих пор TLS 1.2 не может обработать - маловероятно). Похоже все идет к тому, что придется сделать три версии внутреннего УЦ (с ГОСТ-2001 на 3 года, с RSA и ГОСТ-2012 до 2040 года).

Отправлено спустя 22 минуты 16 секунды:
Цитата
Sergey Cheban пишет:
И второй позор - отсутствие бесплатной реализации и прочие vendor lock'и
OpenSSL доберется и до него, вопрос времени.
 
Цитата
two_oceans пишет:
С учетом сказанного, а если так: переключить один жесткий диск на только чтение, скопировать, перевернуть данные на копии, подключить копию к новому хранилищу для чтения,
Я, честно говоря, предпочёл бы уйти от этой темы. Во-первых, здесь это махровый оффтопик, поскольку к ЖКХ не относится никаким боком. Во-вторых, я о ней имею только самое общее представление, на уровне "Мы не можем перейти на новый стандарт C++, поскольку пока не избавились от компьютеров, для которых нет компиляторов, поддерживающих этот стандарт". Плюс, конечно, общая эрудиция и общее представление о том, чем занимается компания. В-третьих, всё, что не общеизвестно, закрыто NDA.

Цитата
two_oceans пишет:
По ГОСТ: проблема даже не в том, что "хочется" ГОСТ - персональные данные передающиеся из одного подразделения в другое подразделение по общей сети провайдера должны быть защищены ГОСТ.
Да, это многое объясняет (тех, кто придумал всю эту возню с персональными данными, поубивал бы). Конкретно, увы, ничего не подскажу: я не сисадмин, со всем этим практически не пересекаюсь.

Цитата
two_oceans пишет:

С ГОСТ-2012 будет проблема при отсутствии реализации в OpenSSL. Хотя и тут есть шанс попробовать библиотеку связывающую OpenSSL и КриптоПро, а вдруг она сумеет из КриптоПро 4.0 ГОСТ-2012 подключить (помечтать не вредно, но с учетом, что библиотека до сих пор TLS 1.2 не может обработать - маловероятно). Похоже все идет к тому, что придется сделать три версии внутреннего УЦ (с ГОСТ-2001 на 3 года, с RSA и ГОСТ-2012 до 2040 года).
Зачем сейчас начинать делать на гост-2001 - не очень понимаю. Тем более с корневым ключом на три года (с учётом, что он окончательно сдохнет через полтора).

Цитата
two_oceans пишет:
Отправлено спустя 22 минуты 16 секунды:
Цитата
Sergey Cheban пишет:
И второй позор - отсутствие бесплатной реализации и прочие vendor lock'и
OpenSSL доберется и до него, вопрос времени.
Реализация гост для openssl есть в виде внешнего engine, подключаемого через конфиг: https://github.com/gost-engine/engine. Но - это и всё, кажется. В NSS (firefox, chrome, thunderbird etc) госта нет, криптопровайдер для windows - только проприетарный cryptopro, и т.д. Российских корневых сертификатов нет по умолчанию в windows и браузерах, хотя процедура включения известна.
 
Цитата
Sergey Cheban пишет:
Я, честно говоря, предпочёл бы уйти от этой темы.
OK.
Цитата
Sergey Cheban пишет:
Тем более с корневым ключом на три года (с учётом, что он окончательно сдохнет через полтора).
1) Приобретение лицензий КриптоПро 4.0/добавление поддержки нового ГОСТ в Openssl займет некоторое время, а переделать каналы связи на туннели планирую до конца года (и неспешно осваивать новый стандарт в течение года).
2)Как я понимаю, запрет на гост-2001 касается схемы подписи и хэша, остальные алгоритмы благополучно переехали в новый ГОСТ, то есть, если 31 декабря 2018 года прекратить выпускать новые сертификаты (выпуск подразумевает их подпись), то выпущенными ранее можно будет работать (в плане шифрования соединения, не используя хеш 2001 года и не подписывая что-либо) еще до 15 месяцев (хотя это максимум срока клиентского сертификата в аккредитованных УЦ, во внутреннем УЦ можно и больше, но не буду зарываться). Срок сертификата УЦ должен перекрывать максимальный срок действия выпущенных им сертификатов чтобы цепочка доверия работала (в случае если не планируется его перевыпуск, на гост-2001 явно не планируется).
Теоретически возможно включить в новый сертификат УЦ сведения о ключе либо хэше старого сертификата, но полагаю это не сработает, если алгоритм хэша отличается. Во вложении пример 2 сертификатов одного УЦ, в сертификате 2014 года указан хэш оригинального сертификата 2012 года.
Общий срок действия сертификата УЦ при этих условиях составит (считая от сегодня до 1 января 2019 плюс 15 месяцев = 1 апреля 2020 года) 2 года 8 месяцев 10 дней, округленно 3 года. Так что через полтора года УЦ все еще будет действующим, но фактически не будет ничего выпускать/отзывать. Собственно, такая фаза (действующий, но не выпускающий новых сертификатов) предусматривается при любом выводе УЦ из использования. Конечно, все сертификаты, которыми нужно подписывать (не только шифровать) должны закончится раньше 1 января 2019 года и пройти замену на сертификаты с ГОСТ-2012.

Есть заминки - подписание списков отзыва (они должны быть подписаны тем же сертификатом УЦ, но если не отзывать, можно поставить срок действия "крайнего" списка отзыва с 31 декабря 2018 года до конца сертификата УЦ) и при авторизации по сертификату малый объем данных тоже подписывается (хотя никуда не сохраняется и дальше своего сервера уходить не будет, так что не такая уж проблема). Естественно, расчеты справедливы для OpenSSL туннелей и старых версий криптопровайдеров, в новый КриптоПро наверняка заложат запрет в том числе на авторизацию с 1 января 2019 года.
Цитата
Sergey Cheban пишет:
Реализация гост для openssl есть в виде внешнего engine, подключаемого через конфиг: https://github.com/gost-engine/engine. Но - это и всё, кажется.
Наверно этого и достаточно, его можно много куда прикрутить. Спасибо за ссылку, вижу, что ГОСТ-2012 там есть.
Цитата
Sergey Cheban пишет:
В NSS (firefox, chrome, thunderbird etc) госта нет
Могу ошибаться, но дело как правило в использовании старой версии OpenSSL (0.9.8, в которой ГОСТ не было - в каких-то программах как внешние библиотеки, в каких-то вкомпилированы в саму программу) и в системе управления сертификатами. КриптоПро, например, выпускает свой клон Firefox с поддержкой ГОСТ. Еще есть dll hell библиотек OpenSSL (они как правило лежат в папке программы, но недавно на ПК с XP обнаружил в windows/system32 обе библиотеки версии 0.9.8, попробовал заменить на 1.0.2 - защита системных файлов на них не сработала, не Майкрософтовские файлы, dll hell в полный рост) и отсутствие на среднем ПК под Windows файла конфигурации OpenSSL и переменной окружения на него указывающей. Есть подозрение, что если проблемы разрешить и настроить, добавить корневые сертификаты, то и в Firefox стандартном (хотя после таких модификаций сложно назвать стандартным) внезапно появится ГОСТ. Проблемы совместимости клиентских сертификатов с Firefox пропускаю.
Цитата
Sergey Cheban пишет:
Российских корневых сертификатов нет по умолчанию в windows и браузерах, хотя процедура включения известна.
Это логично: если Windows гостовские корневые примет в программу, даже если только сертификаты ГУЦ, то должна будет поддерживать алгоритм ГОСТ "из коробки". Однако, это будет означать, что, например, ФСБ получив в ГУЦ сертификат своего УЦ сможет получить некий доступ ко всем ПК с Windows. Американские спецслужбы просто не согласятся добавить ГУЦ, как и другие УЦ, которые не аккредитованы у них. Конечно, частично проблему бы решило включение ГУЦ и поддержки ГОСТ только в русские версии. С браузерами чуть попроще, но фундаментально проблема такая же - кроме включения корневого сертификата нужна еще поддержка алгоритма.

Плюс порой складывается впечатление, что у нас на государственном уровне лоббируют пропиетарные (зато отечественные) средства: криптопровайдера сертифицированного и бесплатного нет, средств XMLDSig тоже, VPN тоже. Зато предлагают их приобрести у определенных компаний. При этом известно, что за достаточную плату можно стать партнером КриптоПро (так поступило Федеральное казначейство, но сумму не знаю) и раздавать лицензии и криптопровайдер бесплатно "во временное пользование" (казначейство раздает для бюджетников, хотя бумажная волокита при этом изрядная).
 
Посмотрел.. оказывается в основной код openssl тоже добавили "Кузнечика" (Grasshopper) и хэш 34.11-2012 (смотрел в стабильных ветках, нашлось в 1.1.0g, в 1.0.2 еще вроде бы нет, точнее добавилось определение в crypto/objects/obj_dat.h) попробую ближайшие дни собрать 1.1.0g (или найти готовую сборку) и проверить есть ли в списке алгоритмов.
 
Подумал еще - 1) новый ГОСТ совершенно не снимает проблемы с параметрами хэша. Их все также не менее 3 наборов; 2) в методическим указаниях по ГИС пока вроде тишина про гост-2012, то есть пока что спешки нет. Без методических я не в курсе какие коды алгоритмов вписать в XML для нового ГОСТа. Вот тут что-то нашлось, но будет ли это приниматься ГИС, не ясно.

По строкам - добавил запись в бинарные файлы. Спохватился, так как понял - если запишу структуру без обработки, то в файл запишется значение указателя на данные строки, а не сами данные. Ранее была поддержка текстовых файлов через совместимость с pchar. Точнее, совместимость не полная: нормально для обычных данных, но если "строка" содержит #0 в середине (например, не кодированные в base64 значения хэша/подписи), то запишется только часть до него.

По cryptoapi - обычно когда функция возвращает истину, не нужно смотреть GetLastError(), но в режиме отладки у меня вставлена функция которая принимает результат cryptoapi и название функции, вызывает GetLastError(), все выводит в консоль, а результат возвращает дальше (в условные операторы).

Выяснились 2 (пока) глюка: 1) после ошибки "не установлен криптопровайдер", даже если второй вызов (другого криптопровайдера) успешен, GetLastError возвращает тот же код, то есть код не обнуляется при успехе. Поэтому теперь делаю SetLastError(0) перед этим. Похоже, надо обнулять после каждой обработанной ошибки, иначе есть шанс получить код ошибки, которая была десяток операций назад; 2) если запросить контекст, ничего не сделать и закрыть его, то возвращается истина, но GetLastError показывает код ошибки "неожиданный конец данных ASN.1". Судя по поиску, у кого-то установка программ вываливается с эти кодом, весьма печально если причина - неочищенный GetLastError после открытия-закрытия контекста.
 
Подвижки за 2 недели:
Разобрался с классом fphttpclient (аналог дельфивского thttpclient как я понимаю), принимает на вход и выдает на выход TStream, Пришлось помучиться со стримами, так как перенос с дельфи оказался не 100% и документация по отличиям сводится к исходнику модуля sysutils, а в нем куча включаемых файликов (подогнаны под разные платформы). В итоге сделал переходник с моего композитного типа строки на ansistring и его передал при создании стрима. С получением результата дело наладилось когда поставил стрим в нулевую позицию перед чтением.
Для отладки всего это богатства написал страничку на php принимающую POST запрос и сохраняющую его в файл. Это тоже оказалось не очевидно: $HTTP_RAW_POST_DATA по умолчанию оказался отключен, если тип содержимого - стандартные данные формы, а по чтению из php://input как оказалось нужно внимательней читать пример file_get_contents(). Подкорректировал скрипт на vbscript для отладки страницы (он как раз и выдает POST как стандартные данные формы). В итоге страничка приняла данные и от скрипта и от fphttpclient.
Далее научился программно искать на портале smev 2 информацию о сервисе (пока только федеральном) - для этого нужно отправить POST запрос с данными поиска в формате AJAX и получить ответ в формате AJAX с мнемоникой и версией сервиса, потом повторить (только уже GET) указав мненонику и версию - в итоге будет AJAX с паспортом сервиса, перечнем операций и их SoapAction-ами, списком узлов СМЭВ, на которых сервис зарегистрирован, включая адреса по которым сервис доступен. Остается только разобрать полученное и выбрать узел, соответствующий региону (или федеральный или 2 тестовых). С региональными сервисами процесс аналогичен, но в запрос чуть отличается.
Попробовал записать полученное в некий файл (кэш) и считывать чтобы не дергать портал при каждом запросе. Сначала попробовал в двоичный файл и ... чтение не вышло. Похоже композитный тип надо еще внимательно дорабатывать. Выяснился странный глюк - выделяю временную строку с 20Мб памяти, считываю из файла - все ОК. Пытаюсь изменить размер целевой строки: копирую указатель на старый буфер, обнуляю исходный указатель (без освобождения), выделяю память, копирую из старого буфера в новый (вот тут наверно ничего не делается так как в большинстве случаев целевая строка пуста) - ОК, освобождаю старый буфер - исключение Invalid pointer operation. Уже проверяю перед освобождением, что IsBadWritePtr возвратил 0/false (то есть права на запись есть) и все равно не-нет да проскочит ошибка освобождения буфера. Как будто его уже освободили или не выделили. А без освобождения - утечка памяти. Про освободили/не выделили сказать пока сложно - на взгляд вроде бы везде явное выделение памяти есть. Однако возможны неявные ситуации. Попробовал включить отображение адресов - так активно выделяются и освобождаются, глазами все не отследить, нужен менеджер памяти для этого.
По ходу отладки уже нашел баг - параметр функции был композитная строка с передачей по значению, по недосмотру туда передавалась строковая константа и компилятор об это даже не пикнул - попытался создать в стеке переменную композитной строки и туда затолкать константу в виде shortstring, типа присваивание же есть. Ага, а выделение памяти я должен в присваивании делать, непонятно как узнав, что память не выделена. Конечно, проверяю указатель на nil и (теперь) limit на ноль, но в стеке могут быть неочищенные значения с прошлого раза, так что гарантии это не дает и указатель может указывать на освобожденную память. Если это "куча", полученная менеджером памяти паскаля, то с точки зрения системы это все же допустимая память, а менеджер памяти определяет несоответствие. Так что выходит надо дорабатывать: или пилить еще один менеджер памяти для конкретного типа или общаться с паскалевским менеджером или запрашивать память напрямую у системы или передавать всегда параметры композитные строки по ссылке (тогда компилятор хотя бы ругнется "при передаче по ссылке тип должен совпадать").

Так что двоичный файл отложил, теперь пишу в xml (точнее подобный) формате. Проблема в принципе такая же, так что пока передаю неинициализированную строку на чтение, по ходу чтения выясняется нужный размер и под него выделяется память. Если нужно перечитать, перед чтением принудительно освобождаются строки.
Далее глюки продолжаются - последняя новость: в строке 152 символа (было несколько добавлений строк), память выделена под 400 Кб и при добавлении еще одной строки или при выводе на экран снова Access violation. Чуть раньше - все выводится без проблем. Вообще на ровном месте.
А на работе еще Семерка, сразу предлагает поискать решение. :lol: Поиск решений отключил, в список исключаемых из отчета программ добавил, но эта ерунда все равно выпадает (теперь в виде кнопки "закрыть программу") и, что самое неприятное, перехватывает исключение раньше чем выйдет информация о строках где сбой от отладочной версии программы. Кто-нибудь знает как вообще отключить такую "заботу" хотя бы для конкретной программы? Что они теперь предполагают, что все отлаживают внешним отладчиком?
 
Цитата
two_oceans пишет:
ОК, освобождаю старый буфер - исключение Invalid pointer operation.
Есть сильное подозрение, что несколько раньше у Вас случился memory overrun и повредились внутренние данные менеджера памяти. После этого всё работает до тех пор, пока испорченные данные не понадобятся менеджеру памяти. Т.е. проблема вообще не в том месте, которое падает.
 
Я так и понимаю, что проблема обычно не в том месте на которое указывает отладка, ну или ошибки в том же месте достаточно тривиальны и находятся до компиляции. Не так давно - выдало ошибку в строке, где конец процедуры и одно только слово "end;". Или когда вываливалось от неявного преобразования параметра - перед вызовом функции вывод на консоль есть, сразу после входа в функцию вывода нет. Точнее падает при попытке вывести параметры, без параметров иногда выводит. Отладчик там вообще выдал, что не узнает из какого исходника и какой строки данный код, вооружившись двумя дизассемблерами (использую старый W32Dasm c отладчиком и IDA Freeware, которая хорошо распознает фреймы стека и стандартные модули паскаля), по ассемблерному коду опознал, что это функция присвоения значения из shortstring в композитную строку. Дальше раскрутил - неявное преобразование.

Спасибо за наводку, посмотрю исходники на предмет того, как проверить и отслеживать состояние встроенного менеджера памяти. У меня была неясная мысль, что есть зависимость сбоя от количества выделенной памяти (вроде не выделять ли самые большие куски из системы напрямую), спасибо что дали мысли определенную форму. Подозреваю, что если дело в менеджере - придется действительно выделять напрямую (через мини-менеджер).
За сегодня накидал мини-менеджер памяти для конкретного типа промежуточным слоем, но ясности это не принесло, кроме того, что теперь время от времени валится на нем, так как выделение/освобождение памяти теперь через него (но проходит через паскалевский менеджер). До сбойного места при склеивании строк пока дело не дошло.
 
Цитата
two_oceans пишет:
За сегодня накидал мини-менеджер памяти для конкретного типа промежуточным слоем, но ясности это не принесло, кроме того, что теперь время от времени валится на нем, так как выделение/освобождение памяти теперь через него (но проходит через паскалевский менеджер).
Попробуйте реализовать вот этот приём, раз уж у Вас есть свой менеджер памяти: https://en.wikipedia.org/wiki/Guard_byte.
Некоторые компиляторы умеют делать это самостоятельно (см. https://en.wikipedia.org/wiki/AddressSanitizer, например), но про паскаль, увы, я ничего не знаю. Ну и не забывайте, что memory overrun может происходить не на тех данных, которые Вы выделяете через свой менеджер памяти, а на каких-то других.
Ещё посмотрите в сторону heaptrc (http://wiki.freepascal.org/heaptrc). Может быть, он поможет найти проблему.
И ещё посмотрите, нет ли у вас аналога функции _heapchk (https://docs.microsoft.com/ru-ru/cpp/c- ... ce/heapchk) или способа её вызвать. Я просто не в курсе.
 
Спасибо. Действительно менеджер паскаля использует что-то подобное, хотя в подробности не вдавался: в начале блока точно пишется скрытая служебная структура, в том числе в ней указан размер блока, что позволяет освобождать память не указывая размер - менеджер сам его считает. И между блоками расстояние достаточное, скорее всего в конце тоже есть служебные маркеры. heaptrc вообще прямо в яблочко, как раз на FreePascal компилирую. Раньше о нем читал, что утечки памяти выявлять удобно, а тут не вспомнил.

Мини-менеджер пока только добавляет адреса в список при выделении и проверяет наличие в списке перед освобождением и сообщает о несоответствиях, конечно маловато, но дополнительная страховка.

Однако, похоже разгадка оказалась вообще совсем в другом (хотя обломы с памятью не исключаю до конца) - компилятор Фрипаскаля понимает разные "диалекты" Паскаля и некоторые управляющие конструкции не работают в одних, но работают в других. Обычно диалект распространяется только на один модуль и смешивание неудобств не доставляет. В частности, в диалекте Дельфи работает обработка исключений try.. except..end; try..finally..end; но не работает объявление операторов. На диалекте Фрипаскаля наоборот. Поэтому модуль длинных строк на одном диалекте, а httpclient на другом. О несоответствии этих конструкций текущему диалекту модуля компилятор должным образом ругается.

Однако, также есть отличия в синтаксисе инициализации/выгрузки модуля от классического BEGIN END (аналог main в си) и специальных ExitProc (каждый модуль сохраняет указатель ExitProc при загрузке, потом восстанавливает при выгрузке и если не NULL вызывает функцию по этому адресу - выходит нечто похожее на стек модулей). Уже давненько привык к варианту секций initialization .. finalization .. end. В этот раз тоже было так записано, модуль работал, когда более-менее отладил его - перешел к следующему, который его использует. И где-то в этот момент видимо изменился диалект, секции initialization и finalization просто стали игнорироваться без каких-либо сообщений об ошибке.
Отправлено спустя 27 минуты 31 секунды:
Фактически, уже чудо что хотя бы что-то работало на непроинициализированном модуле. После очередной отладки обратил внимание, что помимо ошибок доступа еще не читается кэш ответов портала (при чудесах с памятью не удивительно) и вываливаются обращения к EnterCriticalSection (пока отлаживал просто их комментировал - в тестовой программе все равно только одна thread). А InitializeCriticalSection и чтение файла как раз были в inialization. Сложил 2 и 2, вставил туда еще вывод строки на консоль - не выводит, перенес код инициализации и выгрузки в отдельные процедуры, вручную их вызвал - заработало, хотя наверно теперь надо все модули проверить. И уже не знаю на кого злиться - на авторов компилятора, который не предупреждает, что часть кода просто игнорируется или на себя что не использую классику, которая точно реализована в компиляторе.
 
За вчерашний день проверил остальные модули и оказалось еще интереснее: похоже секция initialization вызывается только в модулях напрямую указанных в основной программе. Причем порядок автоматически выстраивается, а если указанный модуль в программе не используется, то он может исключаться. То есть если модуль 1 использует модуле 2, а основная программа использует модуль 1, то сработает инициализация модуля 1, но не модуля 2. Определенная логика прослеживается. В этот раз у меня вышло что в основной программе указаны 2 модуля, а из них еще тянутся 7 штук. Переписал все кроме мини-менеджера на отдельные процедуры инициализации.

Загрузил эталонные примеры хэша в Base64 кодированном виде (для проверки нужен ли переворот хэша для данного криптопровайдера). Мини-менеджер все же определенно нужная вещь! В одном из вызовов перевода хэша в Base64 пропустил взятие адреса буфера и композитный тип перезаписался строкой-результатом. Данные испортились, в том числе указатель на буфер (но длина хэша мала, пострадали соседние переменных, но критического сбоя не произошло). И тут проявил себя мини-менеджер: 1) не дал освободить незарегистрированный адрес буфера (что привело бы к вылету) и вывел об этом сообщение, по которому я нашел ошибку; 2) когда программа закончилась, освободил все неосвобожденные буферы по своим данным. То есть несмотря на переполнение буфера и временную утечку памяти все закончилось хорошо.

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

С присвоениями, кстати, оказалось, что не только композитный тип, но и все структуры, содержащие его, нужно присваивать очень аккуратно - иначе в них будет совсем не то.
 
За неделю с хвостиком есть подвижки: если кратко мини-менеджер немного усовершенствован и может восстанавливать часть данных, однако проверки по тексту еще не расставлены. Проверки в присваивании включены, но замечаний не показали.

Доработал связки по сертификатам, получилась первая рабочая версия (выдающая реальное значение подписи), перед возвращением данных производится проверка подписи - проверка проходит нормально. В процессе где-то пропала информация про сертификат (которая правильно выводилась с заглушкой значения подписи хэшем), но можно считать ма-а-а-аленькой победой. qws Для получения доступа к контейнеру соответствующему сертификату используется CryptAcquireCertificatePrivateKey - возвращает контекст криптопровайдера с контейнером и закрытым ключом. Тут плохая новость в том, хотя возвращает Истина (ошибок нет) и пригодный для работы контекст, но в GetLastError() появляется код ошибки 127 (не найден адрес процедуры) и как обычно он может всплыть только после нескольких операций и разрушить логику работы приложения.

Еще выяснился минус - функция преобразования в base64 из набора cryptoapi вставляет переводы строки (символы 13 и 10), хотя символа 13 вообще не должно быть в каноническом виде, да и обычно значение подписи пишется в одну строчку даже без символа 10. Это не смертельно, так как значение подписи не попадает в "каноничные области" (те места, которые обязаны быть каноничными), но выглядит в итоге не очень хорошо, так что придется переводы строк убрать. Ранее это же было со значением хэша, там значение входило в "каноничную область", но перевод строки был только в конце и его пришлось отрезать. Теперь планирую найти где теряется сертификат и отработать проверку полученного подписанного xml, убирая по пути различные заглушки. Далее останется проверка каноникализации и тестирование на межведе.

Про каноникализацию нашел хорошую статью с примерами как и что делать вручную и под ее вдохновением набросал (но никак не тестировал еще) реализацию, за исключением 2 моментов - обработки атрибутов по умолчанию и замены символьных ссылок. С ними проблема в том, что они указываются в !DOCTYPE и могут быть не включены в документ, а указаны по внешней ссылке, то есть надо загружать документ откуда-то и его обрабатывать, а он может указывать на другой документ и т.д. Есть большие сомнения, что это когда-нибудь встретится в запросах между ИС, тем не менее без этого текст считается не каноничным. В общем, каноникализация оставлена "на сладкое".

Вчера весь день пытался заставить cryptoapi выдать подпись конкретным сертификатом. Идея была в использовании функции поиска сертификата CertFindCertificateInStore с определенным значением в расширенном использовании ключа. Вот только я видимо не понимаю как она работает, потому что она ничего не находит, хотя сертификат с указанным использованием есть и функцией перечисления всех сертификатов находится. В функцию поиска по расширенному использованию передается структура из количества строк и массива pchar значений (rgpszUsageIdentifier - в префиксах Си я не очень разбираюсь, первый раз такой rgpsz вижу, но комментарий стоит array of psz). Уже ее заполнял и как массив указателей на буферы и как указатель на массив указателей на буферы - результат один.

Еще посмотрю MSDN, возможно тут некая заморочка при переносе описания струтуры на Паскаль и придется менять описание. Хранилище используется "Личные" , и при перечислении и при поиске один и тот же дескриптор хранилища. Конечно можно и перечислять все потом проверять критерии, но если есть функция поиска по нужному параметру, зачем такое извращенство. Хотя наверняка при реализации этого будет быстрее понять, что нужно затолкать в структуру. Тут я понял почему некоторые программы идут с рекомендацией устанавливать только один сертификат: снес все остальные сертификаты и сделал через перечисление. Однако не теряю надежды добить поиск сертификата.

Соответственно придется хорошенько подумать относительно аналогичных функций для OpenSSL - поиска и перечисления сертификатов.
 
Цитата
two_oceans пишет:
Вчера весь день пытался заставить cryptoapi выдать подпись конкретным сертификатом. Идея была в использовании функции поиска сертификата CertFindCertificateInStore с определенным значением в расширенном использовании ключа. Вот только я видимо не понимаю как она работает, потому что она ничего не находит, хотя сертификат с указанным использованием есть и функцией перечисления всех сертификатов находится.
Чёрт его знает. Но... А что Вы будете делать, когда у Вас заведутся несколько сертификатов с этим значением в enhanced key usage (из них парочка - устаревшие)? По-моему, правильнее по fingerprint (CERT_FIND_HASH) выбирать.

Цитата
two_oceans пишет:
(rgpszUsageIdentifier - в префиксах Си я не очень разбираюсь, первый раз такой rgpsz вижу, но комментарий стоит array of psz). Уже ее заполнял и как массив указателей на буферы и как указатель на массив указателей на буферы - результат один.
Я тоже такое впервые вижу, но см. [url:3carpevi]https://en.wikipedia.org/wiki/Hungarian_notation[/url:3carpevi] и [url:3carpevi]https://msdn.microsoft.com/en-us/library/aa260976(VS.60).aspx[/url:3carpevi]. RanGe of Pointers to String Zero-terminated.
Вот пример на паскале: [url:3carpevi]http://www.cryptopro.ru/forum2/default.aspx?g=posts&m=10354#post10354[/url:3carpevi].

Цитата
two_oceans пишет:
Соответственно придется хорошенько подумать относительно аналогичных функций для OpenSSL - поиска и перечисления сертификатов.
Вроде, OpenSSL ни про какие хранилища сертификатов не знает. Ему нужен либо сам сертификат в виде блоба, либо, на худой конец, имя файла. В принципе можно и с криптоапи поступить так же, давать ему сертификат в файле, но это не очень хорошо: windows позволяет сохранить сертификат в хранилище без возможности экспорта, это снижает риск утечки приватного ключа.
 
Цитата
Sergey Cheban пишет:
Но... А что Вы будете делать, когда у Вас заведутся несколько сертификатов с этим значением в enhanced key usage (из них парочка - устаревшие)? По-моему, правильнее по fingerprint (CERT_FIND_HASH) выбирать.
Принципиально согласен, отпечаток лучше всего, но с другой стороны его и хранить/передавать очень неудобно. Еще чуть хуже - пара издатель+ серийный номер. Тут задумка была не загружать программу привязкой к сертификату (по крайней мере, пока тест). Сертификат с таким enhanced key usage один и другой не планируется пока. Согласен, что так небезопасно - либо должен явно настраиваться сертификат для автоподписания при смене либо явно подтверждаться пользователем каждый раз.

Случай с устаревшими наверно не принципиален - дальше планируется проверять не истек ли сертификат. Но не нашелся вообще ни один. Другой вопрос, что да, теоретически могут быть несколько действительных сертификатов с одним и тем же enhanced key usage. Даже если брать только enhanced key usage ЭП-ОВ (1.2.643.100.2.2, для смэв, на котором я тренируюсь), то теоретически у одной организации может быть несколько ИС, подключенных к СМЭВ, и для каждой из них нужен отдельный сертификат ЭП-ОВ. Обычно конечно разные ИС на разных серверах, но если вдруг на одном сервере сошлись, то это будет ужас их отличить.

Ситуацию усложняет и то, что ЭП-ОВ не включает персональных данных либо адреса почты и в данном случае CN = наименованию организации (к слову, с таким CN сертификатов с разной комбинацией enhanced key usage и ФИО уже 5 штук). По рекомендациям предусмотрено, что в качестве CN можно использовать наименование или мнемонику ИС, но: 1) УЦ даже не спросил нужно ли нам указывать определенное CN; 2) самый первый сертификат ИС нужно приложить к заявлению на регистрацию и получить в ответ мнемонику (то есть в нем невозможно указать мнемонику, так как она будет выдана позже). В итоге действительно кроме отпечатка либо издателя+номера не остается других способов идентифицировать нужный сертификат.
Цитата
Sergey Cheban пишет:
RanGe of Pointers to String Zero-terminated.Вот пример на паскале: http://www.cryptopro.ru/forum2/default. ... #post10354.
Спасибо за ссылку, но этот пример скорее поможет при перечислении и проверке каждого перечисленного сертификата, чем при задании поиска. Начать можно с того, что у меня в модуле (как и в теме по ссылке) вообще не указано что это массив! Однако это не должно было повлиять на "массив" из одного элемента который в памяти расположен также как просто элемент.

Могу понять почему не указан массив - если установить массив более чем из одного элемента компилятор изменит размер структуры, а если объявить как массив из одного элемента (как обычно делается для массива неизвестной длины), то нужно отключать проверку индексов массива при работе со структурой (иначе вылетит исключение). Так что явно придется менять описание структуры.
Есть еще одна мысль, где искать ошибку - возможно в описании еще и перепутаны одноименные A и W функции и на самом деле надо передать Юникод строку (да еще с двойным нулем в качестве завершающего символа). Хотя как я понимаю должна бы возвратиться ошибка обращения к памяти в этом случае.
Цитата
Sergey Cheban пишет:
Вроде, OpenSSL ни про какие хранилища сертификатов не знает. Ему нужен либо сам сертификат в виде блоба, либо, на худой конец, имя файла.
Хранилище доверенных сертификатов по крайней мере у него точно есть, только на Windows оно обычно не работает, так как путь в большинстве сборок вкомпилирован в стиле Linux. В общем, об этом я и говорю, что при работе с OpenSSL еще и свое хранилище делать.
Цитата
Sergey Cheban пишет:
В принципе можно и с криптоапи поступить так же, давать ему сертификат в файле, но это не очень хорошо: windows позволяет сохранить сертификат в хранилище без возможности экспорта, это снижает риск утечки приватного ключа.
Как выяснилось, одно другому не мешает! Можно передавать сертификат в файле в cryptoapi и получать ссылку на ключ.

Дано: приватный сертификат установлен со ссылкой на закрытый ключ (тут ссылка имеется ввиду не адрес в памяти, а имя и параметры контейнера и криптопровайдера) и хранится в хранилище Windows (закрытый ключ тоже в хранилище либо на токене). Берем сертификат из файла или из памяти (без закрытого ключа, декодированный из base64, если в файде был кодированный), передаем в CertCreateCertificateContext получаем pCertContext (сертификат в закодированном ASN.1 и раскодированном виде - раскодируются основные поля, не все), передаем pCertContext в CryptAcquireCertificatePrivateKey и получаем hCryptProv (полноценный контекст криптопровайдера с ключевой парой, то есть в том числе со ссылкой на закрытый ключ! соответствующий переданному сертификату), который можем использовать для подписи. Для сопоставления есть разные параметры, наиболее удобный - по открытому ключу в сертификате в хранилище и в переданном pCertContext. Побочный положительный эффект - не нужно гадать AT_KEYEXCHANGE или AT_SIGNATURE в контейнере - определяется автоматически и возвращается. То есть даже если в контейнере 2 ключевых пары, то открытый ключ (или сертификат) идентифицирует пару однозначно, а просто имя контейнера - нет.
Если экспорт ограничен - хотя блоб закрытого ключа не получим, но в остальном все так же.

Подпишись на рассылку новостей ЖКХ, а также наших статей!

Спасибо, вы успешно подписались на рассылку!