Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline m.korobov  
#1 Оставлено : 5 апреля 2022 г. 13:05:02(UTC)
m.korobov

Статус: Новичок

Группы: Участники
Зарегистрирован: 04.04.2022(UTC)
Сообщений: 2
Российская Федерация
Откуда: Bars Group

Доброго времени суток! Реализуется интеграция со сторонним сервисом, они присылают подписанные сообщения с данными (Вложение 1), мне нужно проверить пришедшую подпись (Вложение 2). У стороннего сервиса подпись реализуется как-то по-своему, не по формату, т. е. использование SignedXML.Verify() в лоб выдает ошибку.

Была предпринята попытка сделать свою подпись следующим образом:
Код:

crypto_store = pycades.Store()
crypto_store.Open(pycades.CADESCOM_CURRENT_USER_STORE, pycades.CAPICOM_MY_STORE, pycades.CAPICOM_STORE_OPEN_MAXIMUM_ALLOWED)

certificates = crypto_store.Certificates
certificate = certificates.Item(1)

signer = pycades.Signer()
signer.Certificate = certificate
signer.CheckCertificate = True

signedXML = pycades.SignedXML()
signedXML.SignatureMethod = 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256'
signedXML.DigestMethod = 'urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256'
signedXML.SignatureType = pycades.CADESCOM_XML_SIGNATURE_TYPE_ENVELOPED
signedXML.Content = content

signature = signedXML.Sign(self.signer)

(Если прогонять через SignatureType = pycades.CADESCOM_XML_SIGNATURE_TYPE_ENVELOPING, то DigestValue моей подписи не совпадает с DigestValue подписи стороннего сервиса, а для использования SignatureType = pycades.CADESCOM_XML_SIGNATURE_TYPE_TEMPLATE в изначальных данных отсутствуют тэги, либо я так и не понял, как с ним работать)

Получается свой подписанный файл (Вложение 3). Если далее прогнать:
Код:

newSignedXML = pycades.SignedXML()
newSignedXML.Verify(signature)

То проверка проходит успешно. Но если подставить в тэги <SignatureValue> и <X509Certificate> значения из подписи стороннего сервиса, она начинает валиться с ошибкой Invalid Signature. Я заметил, что в моей подписи присутствует тэг <Transforms> со вложениями, а в подписи стороннего сервиса его нет, и т. к. этот тэг лежит внутри <SignedInfo>, то SignatureValue формируется не так, как должен. Возможно, SignedXML.Verify() ожидает наличия этих тэгов, но не находит их, а если я подставляю их значения как есть в то, что получилось в моей подписи - то результат не сходится из-за того, что уже при формировании в стороннем сервисе не было этих тэгов. Но это лишь догадки, я не знаю, как внутри работает SignedXML.Verify().

Еще была попытка сделать проверку с помощью RawSignature.VerifyHash() (Вложение 4). Она точно так же валится с ошибкой Invalid Signature.

Можно ли как-то организовать проверку только по отдельным значениям из подписи стороннего сервиса с помощью PyCades? И если да, то каким образом? Спасибо заранее.

Для справки: сторонний сервис использует Kotlin и определенный свой код для формирования и проверки подписи, т. о. всё точь в точь по их коду я повторить не могу по техническим причинам различия языков.

Вложения:
(1) decoded_message_from_service.xml (535kb) загружен 6 раз(а).
(2) signature.xml (2kb) загружен 2 раз(а).
(3) my_signed_message.xml (537kb) загружен 7 раз(а).
(4) raw_signature.verify_hash.py (3kb) загружен 9 раз(а).
Offline two_oceans  
#2 Оставлено : 6 апреля 2022 г. 7:39:04(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 1,602
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 110 раз
Поблагодарили: 393 раз в 366 постах
Добрый день.
Коллега, из написанного сообщения совсем не понятно какая именно часть подписана Reference URI везде пуст, что подразумевает подпись текущего документа целиком. Для выбора части документа должна быть либо решетка и ID подписываемого фрагмента либо трансформ XPath. Ничего этого нет. Более того, в CDATA вложен целиком другой документ, нет никакой ясности, возможно надо проверять вложенный документ?
Цитата:
Но если подставить в тэги <SignatureValue> и <X509Certificate> ... заметил, что в моей подписи присутствует тэг <Transforms> со вложениями... Возможно, SignedXML.Verify() ожидает наличия этих тэгов
Вставить просто так SignatureValue в другую подпись нельзя - суть в том, что SignatureValue заверяет то, что находится в SignedInfo. После вставки в заготовку с другими трансформами или добавления трансформов в исходную подпись SignatureValue будет нарушено. Потому вставлять SignatureValue и SignedInfo нужно только вместе. Все операции с фрагментом должны быть отражены в виде трансформов при подписании, при проверке стандартные средства делают только то, что указано в трансформах.

Однако с конкретной подписью проблема: если вставлять в документ (как Вы сделали), то обязательно должен быть трансформ enveloped-signature, но в исходной подписи его нет. Если проверять как внешний файл подписи, то в Reference URI должно быть имя основного файла, который проверяется (после имени файла решетка и ID подписываемого фрагмента, если выбирается фрагмент), но в исходной подписи его опять же нет. Поэтому подпись не соответствует стандарту XMLDSIG ни так, ни этак и соответственно проверяться стандартными средствами с полпинка не будет.

Самоделкины ИС часто об имени файла или трансформах забывают и ответ можно проверить только "по их алгоритму". Стоит отметить, что возможна передача ответа и подписи в виде отдельных вложений по HTTP, в этом случае для вложений есть стандарт MTOM over HTTP, который позволяет указать имя файла вложения и даже ID файла, на которые можно ориентироваться при подписании или проверке ответа с отдельной подписью.

Проверять ее по идее можно по частям - то есть сначала в исходной подписи проверить SignatureValue. Например, берете SignedInfo, применяете трансформ из тега CanonicalithationMethod, проверяете с помощью RawSignature (на вход результат CanonicalithationMethod в качестве подписываемых данных, данные обычно в BASE64; сертификат и значение из SignatureValue).

Потом отдельно проверять DigestValue, телепатией догадавшись какой фрагмент взять и какие трансформы над ним сделать. Хотя наверно с эксклюзивной каноникализацией в трансформах в Reference Вы все же переборщили - в ответе есть секции CDATA и они к тому же в windows-1251. В каноничной форме все преобразуется к UTF-8 и есть специальные обработки CDATA, так что получится "фарш" в каноничной форме. Если точно не знаете о необходимости каноникализации - это мне кажется лишнее для внешнего документа, хотя возможно подойдет если брать только вложенный в CDATA.
Цитата:
Можно ли как-то организовать проверку только по отдельным значениям из подписи стороннего сервиса с помощью PyCades? И если да, то каким образом? Спасибо заранее.
Спецификацию/описание их алгоритма подписи прикрепите. В крайнем случае тот исходный код что не можете воспроизвести по причине различия языков.
Offline m.korobov  
#3 Оставлено : 7 апреля 2022 г. 10:05:31(UTC)
m.korobov

Статус: Новичок

Группы: Участники
Зарегистрирован: 04.04.2022(UTC)
Сообщений: 2
Российская Федерация
Откуда: Bars Group

Цитата:
совсем не понятно какая именно часть подписана Reference URI везде пуст, что подразумевает подпись текущего документа целиком.

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

Цитата:
Проверять ее по идее можно по частям - то есть сначала в исходной подписи проверить SignatureValue. Например, берете SignedInfo, применяете трансформ из тега CanonicalithationMethod, проверяете с помощью RawSignature (на вход результат CanonicalithationMethod в качестве подписываемых данных, данные обычно в BASE64; сертификат и значение из SignatureValue).

Была предпринята попытка сделать и так, она была отражена в 4м вложении в первом сообщении. Я преобразовал всё к виду, указанному в описании RawSignature.VerifyHash(), но результатом снова была ошибка. Также сейчас попробовал, согласно Вашему совету, захэшировать весь тэг SignedInfo (как есть, а затем каноникализируя) и отдать его первым аргументом к методу проверки - также падает Invalid Signature.

Цитата:
В крайнем случае тот исходный код что не можете воспроизвести по причине различия языков.

Прикладываю: MessageSigner.kt.txt (10kb) загружен 3 раз(а). (дополнил в расширении .txt, чтобы смочь залить на форум)
Здесь они делают подпись в функции makeSign, проверку в isValidSign. Аналога объекта, возвращаемого в их функции getSignature, в python мне найти не удалось, может, на самом деле он есть. Мне нужно воспроизвести последнюю часть isValidSign, где создается объект Signature, ему отдается публичный ключ сертификата и canonicalizatedSignedInfo, а затем происходит проверка.
Offline two_oceans  
#4 Оставлено : 7 апреля 2022 г. 11:56:49(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 1,602
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 110 раз
Поблагодарили: 393 раз в 366 постах
Автор: m.korobov Перейти к цитате
Пожалуйста, если моя логика неверна - скажите об этом
Если хэш совпал - повезло. Без описания - чистая телепатия. Логика верна наполовину - нет решетки, значит документ целиком. Вот только где взять этот документ - вторая половина, где логика не столь кристально чиста.
Автор: m.korobov Перейти к цитате
Цитата:
совсем не понятно какая именно часть подписана Reference URI везде пуст, что подразумевает подпись текущего документа целиком.
По моему пониманию, именно весь документ целиком и подписывался.
Уточню еще раз, подсветив. Помимо "целиком", есть еще кое-что. Текущего документа - это того документа, в котором расположен тег Signature. Так как подпись висит вообще отдельно это значит, что (после удаления подписи по enveloped-signature) текущий документ пуст. По логике стандарта - подписана пустая строка. Для интерпретации о которой Вы говорите, ожидается хотя бы какое-то упоминание, что надо брать другой документ, например, имя файла. В случае отдельных вложений может быть указано хотя бы URI вложения по MTOM или имя файла вложения.
Автор: m.korobov Перейти к цитате
Цитата:
Проверять ее по идее можно по частям - то есть сначала в исходной подписи проверить SignatureValue. Например, берете SignedInfo, применяете трансформ из тега CanonicalithationMethod, проверяете с помощью RawSignature (на вход результат CanonicalithationMethod в качестве подписываемых данных, данные обычно в BASE64; сертификат и значение из SignatureValue).
Была предпринята попытка сделать и так, она была отражена в 4м вложении в первом сообщении. Я преобразовал всё к виду, указанному в описании RawSignature.VerifyHash(), но результатом снова была ошибка. Также сейчас попробовал, согласно Вашему совету, захэшировать весь тэг SignedInfo (как есть, а затем каноникализируя) и отдать его первым аргументом к методу проверки - также падает Invalid Signature.
Тут бывают такие нюансы: SignatureValue из base64 нужно перевести в байты, потом возможно перевернуть/отзеркалить (поменять первый байт с последним, второй с предпоследним и т.д.), перевести в каждый байт шестнадцатиричный вид (насколько вижу по справке). В принципе можно и наоборот - перевести в шестнадцатиричный вид, потом отзеркалить, но надо понимать, что в шестнадцатиричном виде байт это 2 символа и порядок шестнадцатиричных символов в паре не меняется (биты внутри байта всегда пишут как big endian, так как их расположение не регламентировано). Например, было 1234...9E72 станет после отзеркаливания 729E...3412.

Суть в том, что КриптоПро работает с битами начиная с менее значимого (little endian), а в стандарте подписи указывается с более значимого (big endian). Некоторые надстройки над криптопровайдером это автоматом учитывают, некоторые нет и приходится подбирать нужно ли переворачивать самостоятельно. .NET автоматом переворачивает, через CryptoApi нужно самостоятельно перевернуть. В Джаве это решили вообще интересным способом - в зависимости от порядка хэша и порядка подписи есть 3 метода подписания. При этом четвертый вариант даже если корректен признается недопустимым, но переворота либо подписи либо хэша достаточно чтобы попасть на один из допустимых вариантов. В данном случае значение хэша от SignedInfo в явном виде нигде не указывается (один объект КриптоПро понимает какой порядок в другом объекте КриптоПро, так что если значение хэша не вытаскивали и не устанавливали снова - все ок) и переворачивать можно только подпись. Полагаю, что RawSignature ближе всего к CryptoApi и перевернуть все же надо.
Автор: m.korobov Перейти к цитате
Цитата:
В крайнем случае тот исходный код что не можете воспроизвести по причине различия языков.
Прикладываю: MessageSigner.kt.txt (10kb) загружен 3 раз(а).
Изучу. На первый взгляд, похоже на Джаву.

Отредактировано пользователем 7 апреля 2022 г. 12:11:22(UTC)  | Причина: Не указана

Offline Philarete  
#5 Оставлено : 26 апреля 2022 г. 12:54:58(UTC)
Philarete

Статус: Новичок

Группы: Участники
Зарегистрирован: 18.04.2022(UTC)
Сообщений: 2
Российская Федерация

Автор: two_oceans Перейти к цитате
Тут бывают такие нюансы: SignatureValue из base64 нужно перевести в байты, потом возможно перевернуть/отзеркалить (поменять первый байт с последним, второй с предпоследним и т.д.), перевести в каждый байт шестнадцатиричный вид (насколько вижу по справке). В принципе можно и наоборот - перевести в шестнадцатиричный вид, потом отзеркалить, но надо понимать, что в шестнадцатиричном виде байт это 2 символа и порядок шестнадцатиричных символов в паре не меняется (биты внутри байта всегда пишут как big endian, так как их расположение не регламентировано). Например, было 1234...9E72 станет после отзеркаливания 729E...3412.

Добрый день. Я коллега автора темы и продолжатель этой истории. Попробовал всячески поиграться с данными для RawVerify(), но ничего не дало результата.
SignatureValue был преобразован в шестнадцатиричный вид, разбит по байтам. Пробовал передавать в таком виде, в перевёрнутом. Даже попробовал совместить их, отзеркалив всю строку, но нет. Падает с ошибкой Invalid Signature (0x80090006).

Просмотрел ещё раз документацию по RawSignature.VerifyHash:
Цитата:
Подпись для ключей ГОСТ Р 34.10-2001 должна быть представлена как описано в разделе 2.2.2 RFC 4491 (http://tools.ietf.org/html/rfc4491#section-2.2.2), но в обратном порядке байт.

Здесь указано только для ГОСТ 2001, а если у нас подпись ключа по ГОСТ Р 34.10-2012, есть ли разница в представлении данных?

Отредактировано пользователем 27 апреля 2022 г. 10:12:41(UTC)  | Причина: Не указана

Offline Новожилова Елена  
#6 Оставлено : 27 апреля 2022 г. 10:49:59(UTC)
Новожилова Елена

Статус: Сотрудник

Группы: Администраторы, Участники
Зарегистрирован: 10.12.2008(UTC)
Сообщений: 924
Женщина
Откуда: Крипто-Про

Поблагодарили: 99 раз в 95 постах
Здравствуйте!

Формат подписи для ГОСТ2001 и ГОСТ2012-256 одинаковый:

* В CMS и сертификатах используется представление Big Endian (BE) - в соответствии с RFC4491.
* В CryptoAPI 1.0 используется представление Little Endian (LE) - то есть ровно "задом наперёд".
thanks 1 пользователь поблагодарил Новожилова Елена за этот пост.
Андрей * оставлено 27.04.2022(UTC)
Offline Philarete  
#7 Оставлено : 13 мая 2022 г. 11:23:11(UTC)
Philarete

Статус: Новичок

Группы: Участники
Зарегистрирован: 18.04.2022(UTC)
Сообщений: 2
Российская Федерация

С проверкой подписи наконец разобрались, методом проб и ошибок подобрали набор данных, которые начали проходить проверку.

Автор: two_oceans Перейти к цитате
Потом отдельно проверять DigestValue, телепатией догадавшись какой фрагмент взять и какие трансформы над ним сделать. Хотя наверно с эксклюзивной каноникализацией в трансформах в Reference Вы все же переборщили - в ответе есть секции CDATA и они к тому же в windows-1251. В каноничной форме все преобразуется к UTF-8 и есть специальные обработки CDATA, так что получится "фарш" в каноничной форме. Если точно не знаете о необходимости каноникализации - это мне кажется лишнее для внешнего документа, хотя возможно подойдет если брать только вложенный в CDATA.


Правильно ли я пониманию, что DigestValue это хеш подписываемых данных, полученный по алгоритму, к примеру CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256?
Если захешировать подписываемые данные через HashedData и взять просчитанный хеш по алгоритму, перегнать в кодировку Base64, то он не совпадает с DigestValue из подписи. Но если самим подписать данные через SignedXML с использованием того же алгоритма и вытащить оттуда DigestValue, то он совпадёт. Можно ли как-то получить такой DigestValue, но без подписывания данных? В чём различие хеширований в двух описанных способах по одному алгоритму?




Offline two_oceans  
#8 Оставлено : 16 мая 2022 г. 6:18:52(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 1,602
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 110 раз
Поблагодарили: 393 раз в 366 постах
Добрый день. Разобрались частично, это уже маленькая победа, поздравляю.
Автор: Philarete Перейти к цитате
Правильно ли я пониманию, что DigestValue это хеш подписываемых данных, полученный по алгоритму, к примеру CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256?
Верно. Фрагмент выбирается в Reference атрибут URI, далее смотрятся подчиненные Reference теги. Точный алгоритм должен быть указан в теге DigestMethod атрибут Algorithm, например
Код:
<ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"/>
, то есть если нужна универсальность, то надо сделать некую свою табличку соответствий значений атрибута и констант CADESCOM_HASH_ALGORITHM_*

Если напомнить процедуру точнее, нужно взять подписываемый фрагмент (с учетом контекста предков, но если у Вас весь документ подписывается, то контекст предков пустой, просто берете все). Если берете фрагмент не XML парсером, то следует учесть, что в норме парсер выполняет нормализацию переводов строк (символы 13+10 заменяются на 10, потом одиночные символы 13 заменяются на 10), то есть во взятом фрагменте не должно быть символов с кодом 13. К взятому фрагменту применяются все указанные в <Transforms> трансформы. В случае каноникализации стоит учитывать, что кодировка должна быть UTF-8 если <?xml ?> нет (либо корректно указана в <?xml ?>, в этом случае будет автоматом преобразована к UTF-8). Результат трансформов как поток октетов (то есть текст "лишается прав текста" и трактуется как "байты") идет на хэширование.

В случае плагина рекомендуется указать DataEncoding = ...BASE64_TO_BINARY (оно же 1) и закодировать текст в Base64 перед передачей методу Hash во избежание искажения при передаче в плагин.
Автор: Philarete Перейти к цитате
Если захешировать подписываемые данные через HashedData и взять просчитанный хеш по алгоритму, перегнать в кодировку Base64, то он не совпадает с DigestValue из подписи. Но если самим подписать данные через SignedXML с использованием того же алгоритма и вытащить оттуда DigestValue, то он совпадёт. Можно ли как-то получить такой DigestValue, но без подписывания данных? В чём различие хеширований в двух описанных способах по одному алгоритму?
Если алгоритм совпадает, то никаких различий самого процесса хэширования нет. У алгоритма гост-94 были еще наборы параметров (внутренних констант для вычисления хэша), нужно было следить за оидом параметров. У алгоритма гост-2012 все константы прописаны в самом стандарте, так что попытки изменить оид параметров возвращают ошибку.

Другой вопрос в том, что стандарты гост обычно не регламентируют формат представления результата хэширования программными реализациями. Совсем. В самом стандарте константы и примеры записаны как числа в позиционной системе счисления, то есть наиболее значимый бит (и байт) первым (BIG ENDIAN). В стандарте XMLDSIG аналогичный порядок байт. Модули гост для openssl аналогично.

Однако как отвечали выше КриптоПро CSP на самом нижнем уровне возвращает данные как LITTLE ENDIAN. Некоторые интерфейсы уровнем повыше уже это учитывают и корректируют порядок байт, некоторые нет. Если нет, нужно будет в своем коде "отзеркалить" результат (как описано в моем ответе выше). Для гост-2012 чаще всего приходится "отзеркалить" значение подписи, а вот "отзеркалить" хэш обычно не требуется.

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

Отредактировано пользователем 16 мая 2022 г. 6:55:03(UTC)  | Причина: Не указана

thanks 1 пользователь поблагодарил two_oceans за этот пост.
Санчир Момолдаев оставлено 16.05.2022(UTC)
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.