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

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline roman84  
#1 Оставлено : 6 августа 2018 г. 19:27:29(UTC)
roman84

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

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

Сказал(а) «Спасибо»: 3 раз
Добрый день. Есть задача - сформировать сообщение SOAP с подписанным элементом TimeStamp из заголовка.
В примерах JCP нашёл wss4j\wss4j1_6_3\manager\SOAPXMLSignatureManager_1_6_3.java, который решает задачу подписи SOAP запроса, где подписывается тело запроса:

Код:
        Reference ref = xmlSignatureFactory.newReference("#body",
                xmlSignatureFactory.newDigestMethod("http://www.w3.org/2001/04/xmldsig-more#gostr3411", null));


Если заменить ссылку с #body на #TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918:
Код:
        
        LocalDateTime localDateTime = LocalDateTime.now(ZoneOffset.UTC);
        Element timeStamp =(Element) securityHeader.appendChild(
                doc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "wsu:Timestamp"));
        timeStamp.setAttribute("wsu:Id", "TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918");
        Element createdElement = doc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "wsu:Created");
        createdElement.setTextContent(localDateTime.format(FORMATTER));
        timeStamp.appendChild(createdElement);
        Element expiresElement = doc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "wsu:Expires");
        expiresElement.setTextContent(localDateTime.plusMinutes(10).format(FORMATTER));
        timeStamp.appendChild(expiresElement);
        header.getSecurityHeader().appendChild(timeStamp);

        Reference ref = xmlSignatureFactory.newReference("#TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918",
                xmlSignatureFactory.newDigestMethod("http://www.w3.org/2001/04/xmldsig-more#gostr3411", null));

то в ru.CryptoPro.JCPxml.dsig.internal.dom.DOMURIDereferencer возникает ошибка в строке:
XMLSignatureInput var17 = var15.resolve(var4, var7);

Есть два вопроса.
1.Как правильно использовать Крипто Про JCP для формирования ЭП в моём случае (подпись TimeStamp).
2.Нет ли возможности использовать средства WSS4J для упрощения работы с формированием подписи? - Кажется собирание Security заголовка вручную сейчас избыточно.

Ниже привел пример SOAP запроса, который должен получиться на выходе.
Из него видно, что требуется подписать только элемент Timestamp (см. <ds:Reference URI="#TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918">).

Код:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Header>
		<wsse:Security soap:mustUnderstand="1"
			xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
			xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
			<wsse:BinarySecurityToken
				EncodingType="docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
				ValueType="docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
				wsu:Id="X509-03c419cb-685e-4f0d-a4a3-7d83bd892876">MIIEbDCCBBugAwIB........</wsse:BinarySecurityToken>
			<wsu:Timestamp wsu:Id="TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918">
				<wsu:Created>2017-04-25T10:42:30.326Z</wsu:Created>
				<wsu:Expires>2017-04-25T10:50:30.326Z</wsu:Expires>
			</wsu:Timestamp>
			<ds:Signature Id="SIG-41c97e51-1a40-4589-9a93-da76856eca99" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
				<ds:SignedInfo>
					<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
						<ec:InclusiveNamespaces PrefixList="soap" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
					</ds:CanonicalizationMethod>
					<ds:SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411"/>
					<ds:Reference URI="#TS-2551fb4a-89d1-4bd1-8737-f335ad2b1918">
						<ds:Transforms>
							<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
								<ec:InclusiveNamespaces PrefixList="wsse soap" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
							</ds:Transform>
						</ds:Transforms>
						<ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411"/>
						<ds:DigestValue>nkptIV9P0QzTwaKty6mFWfh5dtr5Yz9rAyKEIm5XMy8=</ds:DigestValue>
					</ds:Reference>
				</ds:SignedInfo>
				<ds:SignatureValue>AGDYUYZXCYSFSADSDSsh</ds:SignatureValue>
				<ds:KeyInfo Id="KI-6a9c0bc3-b053-4033-8549-71acc031d26c">
					<wsse:SecurityTokenReference 
						wsu:Id="STR-f5a2bf7f-9f35-48cd-a058-912ef26188f6"
						xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
						xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
						<wsse:Reference URI="#X509-03c419cb-685e-4f0d-a4a3-7d83bd892876" ValueType="docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
					</wsse:SecurityTokenReference>
				</ds:KeyInfo>
			</ds:Signature>
		</wsse:Security>
	</soap:Header>
	<soap:Body>
		<requestRegistrationData xmlns="irud.gpk.nalog.ru" xmlns:ns2="http://schemas.microsoft.com/2003/10/Serialization/">
			<login>login_irud</login>
			<password>password_irud</password>
			<ownerId>123</ownerId>
			<abonentID>1234567</abonentID>
		</requestRegistrationData>
	</soap:Body>
</soap:Envelope>

Отредактировано пользователем 6 августа 2018 г. 19:29:05(UTC)  | Причина: Не указана

Offline roman84  
#2 Оставлено : 17 октября 2018 г. 19:40:23(UTC)
roman84

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

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

Сказал(а) «Спасибо»: 3 раз
Получили пример приложения на .net которое корректно формирует xmldsig. Пример SOAP запроса:

Код:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"><s:Header><o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><u:Timestamp u:Id="_0"><u:Created>2018-10-16T15:12:19.796Z</u:Created><u:Expires>2018-10-16T15:17:19.796Z</u:Expires></u:Timestamp><o:BinarySecurityToken u:Id="uuid-cca0f69c-de88-4176-9235-c5c36b97b406-1" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">...</o:BinarySecurityToken><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411"/><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411"/><DigestValue>sXZ2C9SwIc+cv4QteR+q7gc6cm8/m2FWDAa7i5wLUCU=</DigestValue></Reference></SignedInfo><SignatureValue>y3v96dx6TyPAjBucXqP9RSZNztcfrozf5CjGbvH9vz1k5SAXGdZx7x5+uZjbmYu1DebHb9YiAZidBG2Z9s1vNw==</SignatureValue><KeyInfo><o:SecurityTokenReference><o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#uuid-cca0f69c-de88-4176-9235-c5c36b97b406-1"/></o:SecurityTokenReference></KeyInfo></Signature></o:Security></s:Header><s:Body>...</s:Body></s:Envelope>


Хотелось бы научиться проверять подпись "руками".
Для этого написал два вспомогательных метода на Java для вычисления хеша и проверки подписи (используется JCP):


Код:
    public static byte[] digest(InputStream inputStream) throws NoSuchAlgorithmException, IOException {
        MessageDigest digest = MessageDigest.getInstance(JCP.GOST_DIGEST_NAME);
        final DigestInputStream digestStream = new DigestInputStream(inputStream, digest);
        while (digestStream.available() != 0) {
            digestStream.read();
        }
        return digest.digest();
    }

    public static boolean verifySimple(byte[] data, byte[] sign, X509Certificate certificate) throws Exception {
        Signature validator = Signature.getInstance(JCP.CRYPTOPRO_SIGN_NAME, JCP.PROVIDER_NAME);
        validator.initVerify(certificate.getPublicKey());
        validator.update(data);
        return validator.verify(sign);
    }


Что делаю:
1. Каноникализирую Timestamp (поле с данными), выходит:
Код:
<u:Timestamp xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" u:Id="_0"><u:Created>2018-10-16T15:12:19.796Z</u:Created><u:Expires>2018-10-16T15:17:19.796Z</u:Expires></u:Timestamp>

2. Считаю его хеш: sXZ2C9SwIc+cv4QteR+q7gc6cm8/m2FWDAa7i5wLUCU= (совпадает хешем в DigestValue).
3. Каноникализирую SignedInfo, выходит:
Код:
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#"><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod><SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411"></SignatureMethod><Reference URI="#_0"><Transforms><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform></Transforms><DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411"></DigestMethod><DigestValue>sXZ2C9SwIc+cv4QteR+q7gc6cm8/m2FWDAa7i5wLUCU=</DigestValue></Reference></SignedInfo>

4. Получаю хеш от каноникализированного представления SignedInfo: AsqckkG8X6Mpyp27Y8CMvVJ9sa48UFi4z+eNmGKNUEw=
5. Выполняю проверку подписи - на этом этапе не могу добиться того что бы проверка подписи проходила.

Где то читал на форуме, что подпись в SignatureValue должна переворачиваться - это тоже пробовал (в т.ч. "перевороты" компонент подписи из 32 байт по отдельности). Даже хеш пробовал перевернуть - ничего не помогает. Прошу совета.

Отредактировано пользователем 17 октября 2018 г. 19:55:59(UTC)  | Причина: уточнение: пример xmldsig из приложения на .Net, проверка подписи из приложения Java

Offline two_oceans  
#3 Оставлено : 18 октября 2018 г. 8:27:32(UTC)
two_oceans

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

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

Сказал(а) «Спасибо»: 110 раз
Поблагодарили: 393 раз в 366 постах
Скорее всего неверен шаг 4 - от канонического вида мало посчитать хэш, надо хэш еще и подписать ключом. По идее это отличается "на глаз" - длина хэша гост-94 32 байта, а длина подписи гост-2001 (в которой используется хэш гост-94) 64 байта. Побочный эффект из-за заранеее известной длины: в кодированном base64 виде у хэша гост-94 всегда один знак равенства в конце, у подписи гост-2001 всегда два знака равенства в конце. У Вас на шаге 4 получается больно короткое значение с один равенством на конце - это и правда хэш, а не подпись. Как можете заметить в SignatureValue значение длинее и с 2 знаками равенства. Данное значение хэша напрямую не фигурирует нигде в xmldsig и его не с чем сравнить. Значение в SignatureValue также напрямую сравнить нельзя, так как при каждом подписании одинаковых данных одинаковым ключом по алгоритмам гост получается разное значение в SignatureValue.

Насчет "переворота" не уверен нужен ли он в данном случае - ведь хэш на шеге 2 у Вас совпал. Значит переворот уже где-то сделан неявно.
Почему вообще переворот нужен? Суть в том, что в cryptoapi Криптопро CSP возвращает и принимает данные хэша/подписи в порядке байтов little endian (для платформы intel такой порядок общепринят в программах), а в стандарте xmldsig указан порядок байт big endian (общепринят в сетевых стандартах). Из-за этого нужно в вычисленном хэше (32 байт, считая с 1) поменять 1-й байт с 32-м, 2-ой с 31-м и так далее до 16-й с 17-м перед кодированием в base64. Перед проверкой соответственно нужно сначала декодировать base64 снова перевернуть. В случае подписи (64 байт, считая с 1) это будет 1-й байт с 64-м, 2-й с 63-м... 32-й с 33-м то есть не надо переворачивать половинками по 32 байта, надо первый с последним, второй с предпоследним и так далее.
thanks 2 пользователей поблагодарили two_oceans за этот пост.
roman84 оставлено 18.10.2018(UTC), heavyside оставлено 08.06.2022(UTC)
Offline roman84  
#4 Оставлено : 18 октября 2018 г. 11:02:52(UTC)
roman84

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

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

Сказал(а) «Спасибо»: 3 раз
Автор: two_oceans Перейти к цитате
Скорее всего неверен шаг 4 - от канонического вида мало посчитать хэш, надо хэш еще и подписать ключом. По идее это отличается "на глаз" - длина хэша гост-94 32 байта, а длина подписи гост-2001 (в которой используется хэш гост-94) 64 байта.


На шаге 4 считаю хеш от SignedInfo, а подпись у меня уже есть - беру из исходного контейнера: <SignatureValue>y3v96dx6TyPAjBucXqP9RSZNztcfrozf5CjGbvH9vz1k5SAXGdZx7x5+uZjbmYu1DebHb9YiAZidBG2Z9s1vNw==</SignatureValue>

Собственно на 5-м шаге пытаюсь проверить, что подпись (которая уже есть и которую каким то образом вычислило приложение) корректна по отношению к хешу от SignedInfo (AsqckkG8X6Mpyp27Y8CMvVJ9sa48UFi4z+eNmGKNUEw=) - и проверка подписи постоянно "не проходит".


Автор: two_oceans Перейти к цитате
Насчет "переворота" не уверен нужен ли он в данном случае - ведь хэш на шеге 2 у Вас совпал. Значит переворот уже где-то сделан неявно.
Почему вообще переворот нужен? Суть в том, что в cryptoapi Криптопро CSP возвращает и принимает данные хэша/подписи в порядке байтов little endian (для платформы intel такой порядок общепринят в программах), а в стандарте xmldsig указан порядок байт big endian (общепринят в сетевых стандартах). Из-за этого нужно в вычисленном хэше (32 байт, считая с 1) поменять 1-й байт с 32-м, 2-ой с 31-м и так далее до 16-й с 17-м перед кодированием в base64. Перед проверкой соответственно нужно сначала декодировать base64 снова перевернуть. В случае подписи (64 байт, считая с 1) это будет 1-й байт с 64-м, 2-й с 63-м... 32-й с 33-м то есть не надо переворачивать половинками по 32 байта, надо первый с последним, второй с предпоследним и так далее.


Спасибо за обстоятельное пояснение.

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

Offline two_oceans  
#5 Оставлено : 18 октября 2018 г. 13:17:07(UTC)
two_oceans

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

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

Сказал(а) «Спасибо»: 110 раз
Поблагодарили: 393 раз в 366 постах
Ясно, наверно я невнимательно прочитал, что хэш от каноничного signedinfo делаете именно для проверки подписи. Как я понимаю, есть 2 варианта установки данных для проверки подписи - передать значение хэша от каноничного signedinfo либо передать сами данные каноничного signedinfo. Здесь показан явно не весь код, неясно что передается в параметр data функции verifySimple. Навскидку можно предположить, что в параметр data должны передаваться сами данные, а передается хэш от данных (так как документацию по объекту Signature в Java я не читал может быть и наоборот: должен передаваться хэш, а передаются данные) и из-за этого проверка проходит неудачно.
thanks 2 пользователей поблагодарили two_oceans за этот пост.
roman84 оставлено 18.10.2018(UTC), heavyside оставлено 08.06.2022(UTC)
Offline roman84  
#6 Оставлено : 18 октября 2018 г. 13:30:52(UTC)
roman84

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

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

Сказал(а) «Спасибо»: 3 раз
Автор: two_oceans Перейти к цитате
Ясно, наверно я невнимательно прочитал, что хэш от каноничного signedinfo делаете именно для проверки подписи. Как я понимаю, есть 2 варианта установки данных для проверки подписи - передать значение хэша от каноничного signedinfo либо передать сами данные каноничного signedinfo. Здесь показан явно не весь код, неясно что передается в параметр data функции verifySimple. Навскидку можно предположить, что в параметр data должны передаваться сами данные, а передается хэш от данных (так как документацию по объекту Signature в Java я не читал может быть и наоборот: должен передаваться хэш, а передаются данные) и из-за этого проверка проходит неудачно.


Да, Вы абсолютно правы. Буквально час назад смог получить корректную подпись. Ранее передавал в функцию валидации хеш, в то время как нужно было передавать сами данные. Вызов validator.update(data) должен был меня натолкнуть на эту мысль, но... Кстати, что бы проверка прошла успешно подпись из SignatureValue нужно было "перевернуть" по описанному вами алгоритму (1-64, 2-63,...,32..33). Ещё раз спасибо!
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.