Стандартный Калкан-апплет не позволяет подписывать SOAP-сообщения. Допустим, перед вами все-таки встала такая или подобная задача...
Теоретически можно расширить апплет, добавив свой метод, но для этого нужно убрать родную подпись апплета и установить свою.
Важно! Самоподписанный сертификат для подписания апплета не подойдет, нужен легально подписанный сертификат.
Дальшейшая практическая инструкция подразумевает, что у вас есть легальный сертификат для подписи апплета, иначе некоторые браузеры могут не принять самоподписанный сертификат.
- Создаем maven-проект. Вот содержимое pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>kz.kesh.blog</groupId>
<artifactId>kalkan-applet</artifactId>
<version>1.2.3</version>
<dependencies>
<dependency>
<groupId>kz.gov.pki</groupId>
<artifactId>knca_applet</artifactId>
<version>0.1.1</version>
</dependency>
</dependencies>
<build>
<finalName>knca_applet_patched</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<!-- Additional configuration. -->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>false</addClasspath>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
<Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
<Built-By>test@kesh.kz</Built-By>
<Trusted-Library>true</Trusted-Library>
<Permissions>all-permissions</Permissions>
<Codebase>*</Codebase>
<Application-Name>Kesh blog</Application-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project> |
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>kz.kesh.blog</groupId>
<artifactId>kalkan-applet</artifactId>
<version>1.2.3</version>
<dependencies>
<dependency>
<groupId>kz.gov.pki</groupId>
<artifactId>knca_applet</artifactId>
<version>0.1.1</version>
</dependency>
</dependencies>
<build>
<finalName>knca_applet_patched</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<!-- Additional configuration. -->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<addClasspath>false</addClasspath>
<addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Application-Library-Allowable-Codebase>*</Application-Library-Allowable-Codebase>
<Caller-Allowable-Codebase>*</Caller-Allowable-Codebase>
<Built-By>test@kesh.kz</Built-By>
<Trusted-Library>true</Trusted-Library>
<Permissions>all-permissions</Permissions>
<Codebase>*</Codebase>
<Application-Name>Kesh blog</Application-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
- Необходимо добавить в локальный репозиторий 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 |
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<ResultWrapper>() {
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;
}
}
} |
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<ResultWrapper>() {
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-файл:
Сигнатура нового метода следующая
ResultWrapper signSoap(final String storagePath,final String alias,final String password,final String xml) |
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 (тоже можно изменить).