Недавно в этом блоге мы открыли тему интеграции со СМЭВ с использованием наших продуктов. Прошлая статья посвящена разработке на платформе .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.
Афанасьев Евгений
Беляев Анатолий
Смирнов Павел