Статистика

Участников проекта 105
Опубликовано статей 78
Отчет по карме. Топ 20

Новости блога

1 29.11.2013  Сегодня самым активным участникам newblog'а был выплачен доход с sape.
7 02.11.2012  Ура! Свешилось, нашему сайту дали тИЦ 10. Спасибо всем кто принимает участие в развитии нашего блога.
8 21.08.2012  Интеграция с sape.ru. Теперь каждый автор статей на newblog автоматически зарабатывает на рекламе.
Все новости

Топ 5 категорий

Программирование 46
Операционные системы 9
Базы данных 4
Туризм 2
Заметки 2

Последние 5 заметок (90)

gullyar - Закладки gullyar
gullyar - Ваша первая закладка
osadchaya - Закладки osadchaya
Ira0231188 - Закладки Ira0231188
Ira0231188 - Закладки Ira0231188

Ссылки

www.freedev.asia

Proxy ЭЦП для Soap (как прикрутить электронно цифровую подпись к сервисам написанным на любом языке(php, java и т.д.)

07.10.2011 20:02 | Просмотров: 4116 | Доход: 76.81 руб. | Комментариев: 0
[Программирование] 
Рейтинг: 5/2

Что делать если сервисы уже написаны и нужно добавить проверку электронно цифровой подписи? А что если сервисы написаны на php, для которого криптопровайдер не предоставляет интерфейсов? Для всего этого подойдет необычное решение описанное в данной статье, в которой я расскажу о своем опыте создания прокси сервера, который умеет проверять xml запросы принятые от клиента soap и отдавать подписанные ответы. Таким образом данный прокси-сервер можно добавить к уже любому существующему проекту, написанному на любом языке.

Создаем проект на java, в MainClass.java:

public static void main(String[] args) throws Throwable {
  ServerSocket sserver = new ServerSocket(4444);
  while (true) {
    Socket sclient = sserver.accept();
    System.out.println("connect"+sclient.getInetAddress());
    new Thread(new Processor(sclient)).start();
  }
}

Порт 4444 будет постоянно прослушиваться и при подключении нового клиента - будет создан поток в процессе которого и будут происходить все необходимые операции. Также желательно использовать sclient.setSoTimeout([мс]) и обрабатывать исключения по SocketTimeoutException. Перейдем к классу Processor.

SocketProcessor(Socket s) throws Throwable {
  this.s = s;
  this.is = s.getInputStream();
  this.os = s.getOutputStream();
}

private String[] readHeader() throws Throwable {
    	String array[] = new String[2];
    	byte[] readedBytes = null;
    	synchronized(is) {
    		int bytesAvailable = is.available();
    		readedBytes = new byte[bytesAvailable];
    		is.read(readedBytes);
    	}
    	String allrequest=new String(readedBytes);
		Pattern p = Pattern.compile(".*\r\n\r\n(.*)", Pattern.DOTALL); 
		Matcher matcher = p.matcher(allrequest);
		if(matcher.matches()) array[0]=matcher.group(1).trim();
		p = Pattern.compile("(.*)\r\n\r\n.*", Pattern.DOTALL);
		matcher = p.matcher(allrequest);
		if(matcher.matches()) array[1]=matcher.group(1).trim();
        return array;
}

Данную функцию мы будем вызавать первой, где разбираем на заголовок запроса(в array[1]) и самой xml(array[2]). Т.к. для проверки ЭЦП нам потребуется именно сама xml без header.

public void run() {
  try {
    String response="",line="";
    String[] array = readHeader();
    String request = array[0];
    SignAndVerify sav = new SignAndVerify();
    Settings.log("Проверка ЭЦП");
    if(sav.verify(request)){
      System.out.println("ЭЦП валидна, данные не искажены");
      System.out.println("xml без ЭЦП");
      request=request.substring(0, request.indexOf("<ds:Signature")).trim();
      request=request+"</soapenv:Envelope>";
      System.out.println(request);
..............
  }
}

Где класс SignAndVerify - немного модифицированный класс из примера с сайта pki.gov.kz (НУЦ РК), но на данном этапе можно использовать любой подходящий для Вас криптопровайдер

public class SignAndVerify {

    public static final String CHARSET = "UTF-8";
    private static final String P12_PATH = "ecp.p12";
    private static final String P12_PASSWORD = "1234";
    private String signedXml="";

    public boolean verify(String xmlforverify) throws Exception{
      IolaProvider iolaProvider = new IolaProvider();
      Security.addProvider(iolaProvider);
      boolean result=false;
      result=SignUtil.verifyXmlFile(xmlforverify);
      System.out.print("verify:"+result);
        if(result){
          X509Certificate certFromSignedXml = CertUtil.getCertificateFromSignedXmlFile(xmlforverify);
          String dn = certFromSignedXml.getSubjectDN().toString();
          System.out.println("DN=" + dn);
          System.out.println("Serial number=" + certFromSignedXml.getSerialNumber());
        }
        return result;
      }
    
    public String sign(String xmlforsign){
      try {
        IolaProvider iolaProvider = new IolaProvider();
        Security.addProvider(iolaProvider);
        String p12File = P12_PATH;
        char[] p12Password = P12_PASSWORD.toCharArray();
        byte[] p12Bytes = FileUtil.getBytesFromFile(p12File);
        KeyStore ks = KeyStore.getInstance("PKCS12", IolaProvider.PROVIDER_NAME);
        ks.load(new ByteArrayInputStream(p12Bytes), p12Password);
        Enumeration aliasesEnum = ks.aliases();
        String alias = null;
        while (aliasesEnum.hasMoreElements()) {
          alias = aliasesEnum.nextElement().toString();
        }
        PrivateKey privateKey = (PrivateKey) ks.getKey(alias, p12Password);
        X509Certificate certificate = (X509Certificate) ks.getCertificateChain(alias)[0];
        signedXml = SignUtil.signXmlString(xmlforsign, privateKey, certificate, CHARSET);
        } catch (Exception e) {
            e.printStackTrace();
        }
          return signedXml;
	}
}

Т.к. при soap запросах в моем случае используется еще и логин\пароль по мимо ЭЦП, то можно создать конфиг, в котором сделать соотношение логин=серийный номер сертификата. Вытащить логин из xml проще всего так:

Pattern p = Pattern.compile(".*<login>(.*)</login>.*", Pattern.DOTALL); 
Matcher matcher = p.matcher(request); 
matcher.group(1)

На этом этапе мы проверили ЭЦП во входящем запросе, проверили соответствует ли логин сертификату, отделили xml от эцп. Теперь самое время отправить "чистый" запрос на реальный soap сервер(крайне желательно чтобы это приложение находилось на том же "железе" что и реальный сервер чтобы не нарушать цепочку безопасности).

URL url = new URL(Settings.getLink());
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream());
wr.write(request);
wr.flush();
BufferedReader rd=null;
try { 
  rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} catch (IOException ex) {
  System.out.println(ex.getMessage());
  //если результат не пришел
}
while ((line = rd.readLine()) != null) {
    response=response+line;
}
System.out.println("Пришел ответ с сервера");
System.out.println(response);

Теперь мы получили "чистый ответ от сервера", можем его подписать и отправить ожидающему клиенту.

response=sav.sign(response);
System.out.println("Ответ от сервера подписанный ЭЦП");
System.out.println(response);
System.out.println("Ответ с ЭЦП отправляется клиенту");
writeResponse(response);

Функция для отправки ответа клиенту - крайне проста и стандартна:

private void writeResponse(String str) throws Throwable {
  String response = "HTTP/1.1 200 OK\r\n" +
                "Server: GmServer/2011-10-07\r\n" +
                "Content-Type: text/html;charset=utf-8\r\n" +
                "Cache-Control: no-cache\r\n"+
                "Content-Length: " + str.getBytes("UTF-8").length + "\r\n" +
                "Connection: close\r\n\r\n";
  String result = response + str;
  os.write(result.getBytes());
  os.flush();
}

Где с помощью str.getBytes("UTF-8").lenght - мы узнаем реальный размер блока data. Если взять просто length - то можно размер можно определить не верно если будет присутствовать кирилица. Вот собственно и все. Клиент отправил xml запрос подписанный ЭЦП, прокси-сервер его получил, проверил и разобрал на составляющие передав xml запрос без ЭЦП реальному серверу. Реальный сервер обработав запрос вернул ответ в виде не подписанной xml прокси-серверу, который подписал ответ и отправил обратно soap клиенту. В статье описан самый минимум данного алгоритма, который нуждается во множестве проверок на различные внештатные ситуации. Также немного изменив структуру можно реализовать асинхронный прием запросов от SOAP клиента.

В закрытой части можно скачать готовый приклад с исходным кодом. (временно не доступно)

Закрытая часть

Чтобы просмотреть закрытую часть и получить консультацию от самого автора статьи необходимо зарегистрироваться!

© jdev
| Комментировать статью |