Показаны сообщения с ярлыком ssl. Показать все сообщения
Показаны сообщения с ярлыком ssl. Показать все сообщения

четверг, 7 марта 2013 г.

HTTPS в Java

В этой заметке расскажу о, на первый взгляд, простой вещи - https соединении.


HTTPS - это по сути обычный HTTP, но данные передаются в зашифрованном виде с помощью SSL/TLS.


Установка соединения происходит в два этапа, соединение всегда инициирует клиент:
  1. Handshake
  2. Data 
На этапе handshake сторонам необходимо договориться о том, какая версия протокола и набор шифров будет использоваться (клиент предлагает список и сервер выбирает из него), а также произвести аутентификацию сервера (обязательно) и клиента (опционально).  После этого стороны договариваются о сессионном ключе (ключах). Поскольку асимметричные алгоритмы шифрования довольно затратны по ресурсам, для передачи данных используются обычно симметричные алгоритмы, которые менее ресурсоемки.

После этого осуществляется собственно обмен данными, зашифрованными с помощью сессионного ключа.

С практической точки зрения клиенту необходимо только решить, доверяет ли он серверу или нет, и предоставить свой сертификат, если нужно.

В java существует специальное хранилище cacerts, в котором уже находятся многие удостоверяющие центры. Это так называемые root CA, которым безусловно доверяют.

При получении серверного сертификата (цепочки сертификатов) клиент проверяет, приводит ли цепочка сертификатов к известному ему CA, которому он доверяет, и если он не находит такую цепочку, выбрасывается эксепшн вида
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.

Так что пути решения проблемы два:
  • Добавить серверный сертификат в cacerts
Для этого необходимо скачать сертификат с сайта с помощью браузера или любого из описанного здесь методов: http://serverfault.com/questions/139728/how-to-download-ssl-certificate-from-a-website например
$openssl s_client -connect ${REMHOST}:${REMPORT} и скопировать в файл все что между строк -----BEGIN CERTIFICATE----- и -----END CERTIFICATE----- (включая их)
Полученный файл сертификата нужно добавить к cacerts командой
$keytool -import -keystore cacerts -alias [myalias] -file server_cert.pem
  • Установить trustManager при создании sslContext.

          SSLSocketFactory socketFactory = createSSLContext().getSocketFactory();

            HttpsURLConnection connection = (HttpsURLConnection) (url).openConnection();
            connection.setSSLSocketFactory(socketFactory);


      private final SSLContext createSSLContext()
            throws NoSuchAlgorithmException, KeyStoreException,
            CertificateException, IOException,
            UnrecoverableKeyException, KeyManagementException {


        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        FileInputStream in = new FileInputStream("server.pem");
        KeyStore trustStore = KeyStore.getInstance("JKS");
        trustStore.load(null);
        try {
            X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
            trustStore.setCertificateEntry("server_alias", cacert);
        } finally {
            IOUtils.closeQuietly(in);
        }

        TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
        tmf.init(trustStore);

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
        return sslContext;
    }


О том, как предоставить клиентский сертификат, в следующий раз.

Полезные ссылки:
http://www.zytrax.com/tech/survival/ssl.html

пятница, 2 марта 2012 г.

Настройка axis клиента на работу с несколькими хостами по протоколу https c клиентскими сертификатами

Итак, пока есть время, быстренько опишу этот опыт.

Axis, использует системные настройки для осуществления соединения по https, т.е. теоретически можно указать системные свойства

System.setProperty("javax.net.ssl.keyStore", "securirty/cacerts");
System.setProperty("javax.net.ssl.keyStorePassword", "");
System.setProperty("javax.net.ssl.trustStore", "security/cacerts");

и клиент будет их использовать, что вполне допустимо, если нужно соединение только с одним хостом. Но что делать, если нужно коннектиться к нескольким хостам, к каждому с разными сертификатами, причем эти сертификаты находятся в разных хранилищах и имеют разные пароли? Вариантов решения проблемы два: первый - использовать jax-ws для генерации клиента, и дальше идет черная магия с конфигурацией, либо использовать свою SSLConnectionFactory, но завязываться на имя хоста. Какой их этих путей лучше, не знаю, я выбрал решение с созданием SSLConnectionFactory, как более простое. идея и часть кода взята отсюда

Итак, создаем стандартных axis клиентов по wsdl файлам, для каждого хоста получатся свои классы клиента. Далее, перед вызовом методов веб сервиса необходимо установить свойство

AxisProperties.setProperty("axis.socketSecureFactory", "org.yourcompany.packagename.CustomSSLSocketFactory");

Содержимое файла CustomSSLSocketFactory.java примерно следующее:

package org.yourcompany.packagename;

import org.apache.axis.components.net.BooleanHolder;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.FileInputStream;
import java.net.Socket;
import java.security.KeyStore;
import java.util.Hashtable;

/**
 * Custom SSL socket factory to use our integrated keystore.
 * 

* Based loosely on org.apache.axis.components.net.SunJSSESocketFactory */ public class CustomSSLSocketFactory extends JSSESocketFactory implements SecureSocketFactory { private static final String FILE_SEPARATOR = System.getProperty("file.separator"); private static final String KEYSTORE_PATH = System.getProperty("java.home") + FILE_SEPARATOR + "lib" + FILE_SEPARATOR + "security" + FILE_SEPARATOR; public CustomSSLSocketFactory(Hashtable attributes) { super(attributes); } @Override public Socket create(String host, int port, StringBuffer otherHeaders, BooleanHolder useFullURL) throws Exception { sslFactory = createSSlSocketFactory(host); return super.create(host, port, otherHeaders, useFullURL); } private SSLSocketFactory createSSlSocketFactory(String host) throws Exception { String keystorePass = null; String keystorePath = null; if (host.equals("google.com")){ keystorePass = "passforgoogle"; keystorePath = "googlestore.jks"; } else if (host.equals("yandex.com")) { keystorePass = "passforyandex"; keystorePath = "yandexstore.jks"; } else if (host.equals("yahoo.com")) { keystorePass = "passforyahoo"; keystorePath = "yahoostore.jks"; } if (keystorePass == null || keystorePath == null) { System.out.println("unknown host "+ host +", cannot create socket factory"); return null; } char[] keystorepass = keystorePass.toCharArray(); FileInputStream is = new FileInputStream(KEYSTORE_PATH + keystorePath); // create required keystores and their corresponding manager objects KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(is, keystorepass); is.close(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, keystorepass); SSLContext sslContext = SSLContext.getInstance("SSL"); sslContext.init(kmf.getKeyManagers(), null, null); return sslContext.getSocketFactory(); } }

Основным недостатком данного метода является то, что нужно каким-то образом задать список хостов и паролей, это может создать трудности при внедрении в системы, требующие безостановочной работы. Как вариант можно запрашивать эти данные из базы или конфигурационного файла.

Следующей на очереди будет описание работы с библиотекой шифрования Signal COM.