Category Archive: ЭЦП

Парсинг DN (Distinguished Names)

В ЭЦП данные о её владельце и изготовителе хранятся в виде Distinguished Names (DN).
Например:

CN=БЛОГОВ БЛОГ,SURNAME=БЛОГОВ,SERIALNUMBER=IIN123456789012,C=KZ,L=АСТАНА,S=АСТАНА,G=БЛОГОВИЧ,O=ТОО \"Рога\, копыта 24\\7\",OU=BIN000111222333

Атрибуты отделяются между собой запятыми, каждый атрибут представляет из себя ключ и значение, разделенные символом равно (=). Так же некоторые символы экранируются, например: \" \,
Предлагаю следующую функцию, которая принимает строку в формате DN и возвращается объект с соответствующими полями и значениями (я использовал angular’овский цикл, но можно заменить на обычный).

function parseDN(value){
    var parts = value
        .replace(/\\,/g,'<comma>')
        .replace(/,/g,'<delim>')
        .replace(/<comma>/g,',')
        .replace(/\\\\/g,'<backslash>')
        .replace(/\\/g,'')
        .replace(/<backslash>/g,'\\')
        .split('<delim>');
    var map = {};
    angular.forEach(parts, function(part){
        var split = part.split('=');
        map[split[0]] = split[1];
    });
    return map;
}

Пример использования:

    var map = parseDN("YOUR_DN_STRING");
    console.log(map.CN);
    console.log(map.SERIALNUMBER);
    console.log(map.O);

Подписание SOAP-сообщения KALKAN-апплетом

Стандартный Калкан-апплет не позволяет подписывать SOAP-сообщения. Допустим, перед вами все-таки встала такая или подобная задача…
Теоретически можно расширить апплет, добавив свой метод, но для этого нужно убрать родную подпись апплета и установить свою.
Важно! Самоподписанный сертификат для подписания апплета не подойдет, нужен легально подписанный сертификат.
Дальшейшая практическая инструкция подразумевает, что у вас есть легальный сертификат для подписи апплета, иначе некоторые браузеры могут не принять самоподписанный сертификат.

  1. Создаем 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>
  2. Необходимо добавить в локальный репозиторий 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 и он должен находиться в текущей директории, откуда выполняется вышеуказанная команда.

  3. Далее необходимо создать пакет 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;
    		}
    	}
    }
  4. Теперь необходимо собрать новый 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 (тоже можно изменить).