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,'')
        .replace(/,/g,'')
        .replace(//g,',')
        .replace(/\\\\/g,'')
        .replace(/\\/g,'')
        .replace(//g,'\\')
        .split('');
    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:
    
    
        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
                            
                        
                    
                
            
        
    
    
  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() {
    					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 (тоже можно изменить).