четверг, 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