
Недавно в этом блоге мы открыли тему интеграции со СМЭВ с использованием наших продуктов. Прошлая статья посвящена разработке на платформе .NET.
В продолжение темы мы хотим рассмотреть пример взаимодействия со СМЭВ с использованием платформы Java.
Для запуска примера необходимо:
-
установить JRE версий 1.6 или 1.7,
-
скачать WSS4J версии 1.6.3 или 1.6.6,
-
создать проект и подключить библиотеки WSS4J,
-
установить КриптоПро JCP версии 1.0.52 или 1.0.53 в используемую JRE,
-
скачать и подключить сервис-провайдер CryptoProXMLDSigRI,
-
сформировать ключ и сертификат для подписи.
Начнём!
Входящее сообщение выглядит так:
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
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">
<S:Header>
<wsse:Security S:actor="http://smev.gosuslugi.ru/actors/smev"
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"
ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"
wsu:Id="SenderCertificate">
</wsse:BinarySecurityToken>
</wsse:Security>
</S:Header>
<S:Body wsu:Id="body">
<!-- Тело запроса-->
</S:Body>
</S:Envelope>
Предварительно нужно выполнить инициализацию сервис-провайдера для подписи XML:
// Инициализация Transforms.
com.sun.org.apache.xml.internal.security.Init.init();
// Инициализация сервис-провайдера.
if(!JCPXMLDSigInit.isInitialized()) {
JCPXMLDSigInit.init();
}
Загружаем необходимые для работы данные (ключ, сертификат, сообщение):
// Инициализация ключевого контейнера и получение сертификата и закрытого ключа.
KeyStore keyStore = KeyStore.getInstance(JCP.HD_STORE_NAME);
PrivateKey privateKey = (PrivateKey)keyStore.getKey(ALIAS, PASSWORD);
X509Certificate cert = (X509Certificate)keyStore.getCertificate(ALIAS);
// Подготовка сообщения: в данном случае — это чтение сообщения из файла message.xml в кодировке UTF-8.
MessageFactory mf = MessageFactory.newInstance();
SOAPMessage message = mf.createMessage();
SOAPPart soapPart = message.getSOAPPart();
FileInputStream is = new FileInputStream("message.xml");
soapPart.setContent(new StreamSource(is));
message.getSOAPPart().getEnvelope().addNamespaceDeclaration("ds", "http://www.w3.org/2000/09/xmldsig#");
Document doc = message.getSOAPPart().getEnvelope().getOwnerDocument();
Добавляем заголовки для помещения информации о подписи:
WSSecHeader header = new WSSecHeader();
header.setActor("http://smev.gosuslugi.ru/actors/smev");
header.setMustUnderstand(false);
header.insertSecurityHeader(message.getSOAPPart().getEnvelope().getOwnerDocument());
// Элемент подписи.
Element token = header.getSecurityHeader();
Далее необходимо создать экземпляр сервис-провайдера для подписи документа:
// Загрузка провайдера. Provider xmlDSigProvider = new ru.CryptoPro.JCPxml.dsig.internal.dom.XMLDSigRI();
Добавляем описание преобразований над документом и узлом SignedInfo согласно методическим рекомендациям СМЭВ. К каноническому виду приводим с помощью алгоритма http://www.w3.org/2001/10/xml-exc-c14n#:
final Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
XMLSignatureFactory fac = XMLSignatureFactory.getInstance("DOM", xmlDSigProvider);
Преобразования над узлом ds:SignedInfo:
List<Transform> transformList = new ArrayList<Transform>(); Transform transformC14N = fac.newTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS, (XMLStructure) null); transformList.add(transformC14N);
Добавляем ссылку на подписываемый узел с идентификатором "body":
// Ссылка на подписываемые данные с алгоритмом хеширования ГОСТ 34.11.
Reference ref = fac.newReference("#body", fac.newDigestMethod("http://www.w3.org/2001/04/xmldsig-more#gostr3411", null),
transformList, null, null);
Задаём алгоритм подписи:
SignedInfo si = fac.newSignedInfo( fac.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE,
(C14NMethodParameterSpec) null), fac.newSignatureMethod("http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411", null), Collections.singletonList(ref));
В качестве алгоритма хэширования был задан алгоритм ГОСТ Р 34.11-94, а алгоритма подписи — ГОСТ Р 34.10-2001.
Создаём узел ds:KeyInfo с информацией о сертификате:
KeyInfoFactory kif = fac.getKeyInfoFactory(); X509Data x509d = kif.newX509Data(Collections.singletonList((X509Certificate) cert)); KeyInfo ki = kif.newKeyInfo(Collections.singletonList(x509d));
Подписываем данные в элементе token:
javax.xml.crypto.dsig.XMLSignature sig = fac.newXMLSignature(si, ki); DOMSignContext signContext = new DOMSignContext((Key) privateKey, token); sig.sign(signContext);
Следующий этап — поместить узел ds:Signature и сертификат (X509Certificate) в узел wsse:Security, причём сертификат нужно удалить из ds:KeyInfo и оставить там ссылку на wsse:BinarySecurityToken с сертификатом:
// Узел подписи Signature.
Element sigE = (Element) XPathAPI.selectSingleNode(signContext.getParent(), "//ds:Signature");
// Блок данных KeyInfo.
Node keyE = XPathAPI.selectSingleNode(sigE, "//ds:KeyInfo", sigE);
// Элемент SenderCertificate, который должен содержать сертификат.
Element cerVal = (Element) XPathAPI.selectSingleNode(token, "//*[@wsu:Id='SenderCertificate']");
cerVal.setTextContent(XPathAPI.selectSingleNode(keyE, "//ds:X509Certificate", keyE).getFirstChild().getNodeValue());
// Удаляем содержимое KeyInfo
keyE.removeChild(XPathAPI.selectSingleNode(keyE, "//ds:X509Data", keyE));
NodeList chl = keyE.getChildNodes();
for (int i = 0; i < chl.getLength(); i++) {
keyE.removeChild(chl.item(i));
}
// Узел KeyInfo содержит указание на проверку подписи с помощью сертификата SenderCertificate.
Node str = keyE.appendChild(doc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "wsse:SecurityTokenReference"));
Element strRef = (Element)str.appendChild(doc.createElementNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "wsse:Reference"));
strRef.setAttribute("ValueType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3");
strRef.setAttribute("URI", "#SenderCertificate");
header.getSecurityHeader().appendChild(sigE);
Теперь документ готов к отправке.
Семь раз проверь, один — отрежь.
Резать мы конечно ничего не будем, но вот проверить подпись в сообщении из СМЭВ перед его обработкой необходимо. Хотя бы один раз.
Проверка подписи производится следующим образом:
// Получение узла, содержащего сертификат.
final Element wssecontext = doc.createElementNS(null, "namespaceContext");
wssecontext.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
NodeList secnodeList = XPathAPI.selectNodeList(doc.getDocumentElement(), "//wsse:Security");
// Поиск элемента сертификата в блоке BinarySecurityToken.
Element r = null;
Element el = null;
if( secnodeList != null&&secnodeList.getLength()>0 ) {
String actorAttr = null;
for( int i = 0; i<secnodeList.getLength(); i++ ) {
el = (Element) secnodeList.item(i);
actorAttr = el.getAttributeNS("http://schemas.xmlsoap.org/soap/envelope/", "actor");
if(actorAttr != null&&actorAttr.equals("http://smev.gosuslugi.ru/actors/smev")) {
r = (Element)XPathAPI.selectSingleNode(el, "//wsse:BinarySecurityToken[1]", wssecontext);
break;
}
}
}
if(r == null) {
return;
}
// Получение сертификата.
final X509Security x509 = new X509Security(r);
// Создаем сертификат.
cert = (X509Certificate) CertificateFactory.getInstance("X.509")
.generateCertificate(new ByteArrayInputStream(x509.getToken()));
if (cert == null) {
throw new Exception("Сертификат не найден.");
}
System.out.println("Verify by: " + cert.getSubjectDN());
// Поиск элемента Signature.
NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature");
if (nl.getLength() == 0) {
throw new Exception("Не найден элемент Signature.");
}
// Задаем открытый ключ для проверки подписи.
fac = XMLSignatureFactory.getInstance("DOM", xmlDSigProvider);
DOMValidateContext valContext = new DOMValidateContext(KeySelector.singletonKeySelector(cert.getPublicKey()), nl.item(0));
javax.xml.crypto.dsig.XMLSignature signature = fac.unmarshalXMLSignature(valContext);
// Проверяем подпись и выводим результат проверки.
System.out.println( "Verified: " + signature.validate(valContext));
Сертификат в примере извлекается из узла wsse:BinarySecurityToken, и его открытый ключ используется для проверки подписи.
Полный текст примера и необходимые библиотеки можно скачать по ссылке.
To be continued...
В следующей серии мы вернёмся в мир .NET, где вас ждёт пример работы со СМЭВ с использованием WCF.
Афанасьев Евгений
Беляев Анатолий
Смирнов Павел