Стандартный Калкан-апплет не позволяет подписывать SOAP-сообщения. Допустим, перед вами все-таки встала такая или подобная задача...
Теоретически можно расширить апплет, добавив свой метод, но для этого нужно убрать родную подпись апплета и установить свою.
Важно! Самоподписанный сертификат для подписания апплета не подойдет, нужен легально подписанный сертификат.
Дальшейшая практическая инструкция подразумевает, что у вас есть легальный сертификат для подписи апплета, иначе некоторые браузеры могут не принять самоподписанный сертификат.
Создаем maven-проект. Вот содержимое pom.xml:
4.0.0
kz.kesh.blog
kalkan-applet
1.2.3
kz.gov.pki
knca_applet
0.1.1
knca_applet_patched
org.apache.maven.plugins
maven-compiler-plugin
3.2
1.7
1.7
${project.build.sourceEncoding}
org.apache.maven.plugins
maven-shade-plugin
2.4.1
package
shade
*:*
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
org.apache.maven.plugins
maven-jar-plugin
2.4
false
false
true
true
*
*
test@kesh.kz
true
all-permissions
*
Kesh blog
Необходимо добавить в локальный репозиторий maven jar-ку апплета, которую будем прокачивать. Это можно сделать командой:
mvn install:install-file -Dfile=knca_applet-0.1.1.jar -DgroupId=kz.gov.pki -DartifactId=knca_applet -Dversion=0.1.1 -Dpackaging=jar
У вас должен быть в наличии файл knca_applet-0.1.1.jar и он должен находиться в текущей директории, откуда выполняется вышеуказанная команда.
Далее необходимо создать пакет kz.gov.pki.knca.applet и разместить в нем наш новый класс SuperKalkanApplet:
package kz.gov.pki.knca.applet;
import kz.gov.pki.kalkan.Storage;
import kz.gov.pki.kalkan.jce.provider.KalkanProvider;
import kz.gov.pki.knca.applet.exception.AECodes;
import kz.gov.pki.knca.applet.exception.AppletException;
import kz.gov.pki.knca.applet.utils.KeyStoreUtil;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.transforms.Transforms;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.FileInputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.security.AccessController;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PrivilegedAction;
import java.security.cert.X509Certificate;
public class SuperKalkanApplet extends MainApplet{
@SuppressWarnings("unused")
public ResultWrapper signSoap(final String storageName,final String storagePath,final String alias,final String password,final String xml){
return AccessController.doPrivileged(
new PrivilegedAction() {
public ResultWrapper run() {
try {
Document doc = xmlToDoc(xml);
Node envelope = doc.getFirstChild();
Node header = null;
Node body = null;
for (int i = 0; i < envelope.getChildNodes().getLength(); i++) {
Node node = envelope.getChildNodes().item(i);
if ("Header".equals(node.getLocalName())) {
header = node;
} else if ("Body".equals(node.getLocalName())) {
body = node;
}
}
if (header == null) {
header = doc.createElement(envelope.getPrefix() + ":Header");
envelope.insertBefore(header, body);
}
if (body == null) throw new RuntimeException("Body not found");
Attr attr = doc.createAttribute("id");
attr.setValue("body");
body.getAttributes().setNamedItem(attr);
((Element) body).setIdAttributeNode(attr, true);
Storage storage = getStorage(storageName);
KeyStore keyStore = KeyStoreUtil.getKeyStore(storage, storagePath, password.toCharArray(), getProvider());
PrivateKey key = KeyStoreUtil.getPrivateKey(storage, storagePath, alias, password.toCharArray(), getProvider());
X509Certificate certificate = (X509Certificate) keyStore.getCertificate(alias);
String signMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
String digestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
if(!"RSA".equals(key.getAlgorithm())){
signMethod = "http://www.w3.org/2001/04/xmldsig-more#gost34310-gost34311";
digestMethod = "http://www.w3.org/2001/04/xmldsig-more#gost34311";
}
XMLSignature signature = new XMLSignature(doc, "", signMethod);
Transforms transforms = new Transforms(doc);
transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
signature.addDocument("#body", transforms, digestMethod);
signature.addKeyInfo(certificate);
NodeList nodes = header.getChildNodes();
if (nodes.getLength() > 0) {
Node node = header.getFirstChild();
header.insertBefore(signature.getElement(), node);
} else {
header.appendChild(signature.getElement());
}
signature.sign(key);
ResultWrapper rw = new ResultWrapper();
String signedXml = docToText(doc);
System.out.println(signedXml);
rw.setResult(signedXml);
return rw;
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
return new ResultWrapper(e.getMessage());
}
}
}
);
}
private static Document xmlToDoc(String xmlStr) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder;
try
{
builder = factory.newDocumentBuilder();
return builder.parse( new InputSource( new StringReader( xmlStr ) ) );
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String docToText(Document document) throws Exception {
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(document),new StreamResult(writer));
return writer.getBuffer().toString();
}
private Storage getStorage(String storageName) throws AppletException {
Storage storage = Storage.get(storageName);
if(storage == null) {
System.err.println("Unknown storage name : " + storageName);
throw new AppletException(AECodes.UNKNOWN_STORAGE.toString());
} else {
return storage;
}
}
}
Теперь необходимо собрать новый jar-файл:
mvn clean package
Сигнатура нового метода следующая
ResultWrapper signSoap(final String storagePath,final String alias,final String password,final String xml)
storagePath - путь к подписи
alias - идентификатор сертификата
password - пароль
xml - SOAP-сообщение для подписи
При внедрении апплета в html нужно указать
knca_applet_patched.jar (или ваше название) и
kz.gov.pki.knca.applet.SuperKalkanApplet (тоже можно изменить).