SSL, Java, Tomcat, Https...

30.11.2007
Последняя модификация: 29.03.2020
Иванов Аркадий

 

Секретить сегодня потоки данных через Internet к приложению стоит. Анализаторы траффика легко отдадут в чужие руки ваши пароли, данные, ... 
Сама тема достаточно объёмна. Я только отмечу те вещи, которые пришлось делать, и они получались без проблем.

Немного теории.

Принцип, на котором базируется сегодня безопасная передача данных в Internet, основан на находке математиков: 

  1. Есть алгоритм, который позволяет шифровать и дешифровать данные с помощью 2-х взаимосвязанных, но разных ключей. Нужно сгенерировать 2 взаимосвязанных ключа (ключ - набор цифр), которые используются отдельно для шифрации и отдельно для дешифрации данных.
  2. Первый ключ называется открытым. С его помощью можно шифровать данные. Дешифровать данные с его помощью не удастся. Для алгоритма шифровки нет обратной математической функции. Если враг имеет открытый ключ и зашифрованные данные, то расшифровать он может только методом перебора. На сегодня мощность современных компьютеров не позволяет сотворить этот перебор в разумный срок.
  3. Второй ключ называется секретным. С его помощью можно дешифровать данные, зашифрованные с помощью открытого ключа.

Исходя из вышеописанного, у нас есть алгоритм, когда 2-м компьютерам, связанным через небезопасный Интернет, можно установить безопасную связь, даже не имея предварительно каких-либо секретных шифровальных ключей, которые секретный агент провёз с собой на дискетте, рискуя жизнью и пряча дискетту от ЦРУ и КГБ:

Шаг 0. Компьютер А и компьютер Б оснащены одинаковым алгоритмом шифровки/дешифровки. (Программный пакет называется OpenSSL). 
Шаг 1. Компьютер A генерирует секретный и открытый ключ. 
Шаг 2. Компьютер А передаёт открытый ключ через Интернет компьютеру Б. 
Шаг 3. Компьютер Б шифрует данные с помощью открытого ключа. 
Шаг 4. Компьютер Б передаёт зашифрованные данные через Интернет компьютеру А. 
Шаг 5. Компьютер А расшифровывает данные при помощи своего секретного ключа. 

Перехват данных врагом на любом этапе передачи через Интернет ничего ему не даёт. :-)

Сертификаты, ключи в SSL.

SSL - Secure Socket Layer. Уровень безопасных сокетов.  То есть, это  механизмы защиты  на уровне соединения между сокетами.

При SSL-соединении клиента с сервером (например броузер Seamonkey хочет получить файл у Web-сервера Apache по протоколу https) используются следующие механизмы: 
1. Сервер передаёт клиенту свой сертификат. Это не просто открытый ключ. В сертификат входит открытый ключ сервера, информация о сервере (в частности имя хоста, для которого сгенерёны открытый и закрытый ключ), информация о центре, который подписал эти ключи. В мире есть специальные фирмы (например, VeriSign), которым вы посылаете свой открытый ключ для подписи. Вы платите этой фирме, VerySign проверяет, что вы не жулик и ваш веб-сервер не жульнический и снабжает ваш ключ своей цифровой подписью и сроком действия. Проверка цифровых подписей VerySign, Thawte, ... и др. центров, которые получит броузер в присланном сертификате, встроены в большинство броузеров. Таким образом броузеры дополнительно могут проверить, что ваш сайт присылает им сертификат (открытый ключ, с доп. информацией, подписанный специальной фирмой), который не просто позволяет шифровать соединение, но ещё и то, что вам можно доверять, то, что ваш сайт не подменён хакером.

2. Клиент(программа, например, Seamonkey) проверяет, насколько сертификат соответствует тому, что ожидалось - подписан ли он известным центром типа VerySing, не просрочен ли срок действия, соответствует ли он имени того сервера, от которого вы его получили. Если что-то не так, броузеры обычно выдают пользователю окно предупреждения и предлагает просмотреть данные сертификата. Пользователь может принять сертификат, может отклонить.

3. Для данной сессии генерируется специальный шифровальный симметричный ключ. Это ключ, который будет использоваться и для шифрации и для дешифрации данных. Для передачи этого симметричного ключа через Интернет используется открытый ключ. Так что перехватить его не получится.

Замечание. Сертификат необязательно должен быть подписан специальным центром типа VerySign. При его создании вы можете его подписать сами. Но, поскольку броузеры не знают такого центра, как ваш, они будут выдавать предупреждение о том, что к полученному сертификату от вашего сервера пользователю стоит присмотреться, м.б. он сфальсифицирован.

 

Генерация сертификатов для JAVA.

Сертификаты генерируются программой keytool и помещаются в специальное хранилище. Хранилище - это файл (обычно.keystore), который обычно расположен в домашнем каталоге пользователя. Создание самоподписанного сертификата:

keytool -genkeypair -alias mysert -keyalg RSA -validity 365

Здесь генерется пара ключей - секретный+публичный (-genkeypair). 
В хранилище идентификатором этого сертификата будет "mysert". Работа с сертификатом в хранилище производится по его алиасу (-alias). 
Алгоритм, для которого созданы эти ключи (-keyalg) называется RSA. 
Сертификат будет действителен 365 дней (-validity). 

При создании сертификата вы получаете обычный диалог: 

 

Enter keystore password:
Re-enter new password:
What is your first and last name?
  [Unknown]:  myhost.mydomain
What is the name of your organizational unit?
  [Unknown]:  IT
What is the name of your organization?
  [Unknown]:  MyOrg
What is the name of your City or Locality?
  [Unknown]:  MyCity
What is the name of your State or Province?
  [Unknown]:  MyState
What is the two-letter country code for this unit?
  [Unknown]:  ru
Is CN=myhost.mydomain, OU=IT, O=MyOrg, L=MyCity, ST=MyStat, C=ru correct?
  [no]:  yes

Если вы впервые создаёте сертификат, будет создано хранилище и для него будет пароль. 
Совершенно потрясный вопрос: "What is your first and last name?" 
На него должен быть очень точный ответ: полное имя хоста, на котором будет установлен этот сертификат. SSL на клиенте будет проверять совпадение имени в сертификате и имени хоста, от которого он получил его. Они должны совпададать. Минимальная проблема при несовпадении - ваш броузер замучает вас вопросами о правильности сертификата. Некоторые программные пакеты, в которых есть поддержка SSL, просто не будут работать.

Примечание:Можно программно порешать проблему с неправильным именем хоста в сертификате, используя HttpsURLConnection.setDefaultHostnameVerifier(), но не всякий исходный код в чужом пакете можно стройно откорректировать.

 

Хранилище сертификатов.

Итак, по умолчанию для хранилища используется файл .keystore, который лежит в домашнем каталоге. 
Для keytool можно указать параметр:

-keystore keystorefile

, где keystorefile - полный путь к файлу хранилища. 

У хранилища может быть пароль. Для keytool пароль можно указать в командной строке:

-storepass secretpassord

 

Использование SSL и сертификатов в Tomcat.

Сертификат в сервере приложений вам понадобится в описании коннектора, если вы решили использовать SSL. Вот так должно выглядеть описание коннектора:

<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true"
    maxThreads="150" scheme="https" secure="true"
    clientAuth="false" sslProtocol="TLS"
    keystoreFile="/home/myuser/.keystore"
    keystorePass="mypass"

 

Если в Tomcat не указан файл хранилища, по умолчанию используется файл .keystore из домашнего каталога пользователя из под которого запущен Tomcat. 
Если не указан пароль, то используется пароль "changeit". Пароль на хранилище и на сертификат должны совпадать.

Для того, чтобы включить обязательное использование SSL в вашем приложении в webapps/APPNAME/WEB-INF/web.xml необходимо настроить следующие теги:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>ALL</web-resource-name>
      <url-pattern>/*</url-pattern>
    </web-resource-collection>
  <user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
  </user-data-constraint>
</security-constraint>

Даже если вы будете обращаться с броузера по HTTP, Tomcat сделает redirect на HTTPS-коннектор.

 

Часть сайта можно сделать защищённой, часть нет:

<security-constraint>
        <web-resource-collection>
            <web-resource-name>SECURED</web-resource-name>
            <url-pattern>/secured/*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
                <transport-guarantee>CONFIDENTAL</transport-guarantee>
        </user-data-constraint>
</security-constraint>

В примере доступ к файлам из каталога приложения /secured будет только через SSL-коннектор.

 

Сертификат для сайта надо импортировать в хранилище с именем алиаса "tomcat".

 

По умолчанию Tomcat использует из хранилища сертификат с alias-ом "tomcat".

Если у сайта несколько имён (например, необходимо отлаживать сайт внутри сети и иметь доступ снаружи
и при этом сайты имеют разные имена), то следует создать несколько сертификатов под имена сайтов и сделать
в конфигурации server,xml Tomcat-а несколько SSL-коннекторов, в которых указать какие сертификаты использовать.

Пример конфигурации в server.xml:

<Connector port="9843" protocol="HTTP/1.1" SSLEnabled="true".
maxThreads="150" scheme="https" secure="true".
clientAuth="false" sslProtocol="TLS".
keyAlias="tomcat"
keystoreFile="/home/tomcat/.keystore"
keystorePass="*******"
/>
<Connector port="9844" protocol="HTTP/1.1" SSLEnabled="true".
maxThreads="150" scheme="https" secure="true".
clientAuth="false" sslProtocol="TLS"
keyAlias="webapp"
keystoreFile="/home/tomcat/.keystore"
keystorePass="********"
/>

В примере разрешены SSL-соединения на порты 9843 и 9844, Если соединение приходит на порт 9843, то
используется сертификат с алиасом tomcat, если на порт 9844, то используется алиас webapp.

 

 

 Доверяемые центры сертификации.

Некоторые Java-пакеты (например, HTTPClient) могут отказаться работать с вашими сертификатами, если у вас самоподписанный сертификат. Эту проблему можно обойти следующим образом: 

  1. Извлечь сертификат в файл "mycert.cer" из хранилища: 
    keytool -exportcert -file mycert.cer -alias mycert
  2. Добавить его в список сертификатов, которым мы доверяем в хранилище доверяемых сертификатов в $JAVA_HOME/jre/lib/security/cacerts: 
     
    keytool -import -trustcacerts -alias mycert -file mycert.cer

Эту проблему можно решить чисто программным путём в Java, работая с классом X509TrustManager.

При запуске Java-приложения в командной строке можно указать файл хранилища доверяемых сертификатов и пароль доступа к нему: 

-Djavax.net.ssl.trustStore=trustedcert -Djavax.net.ssl.trustStorePassword=trustpass

 

  Использование сертификатов OpenSSL.

Другая конфигурация SSL в Tomcat предполагает использование ключей OpenSSL (формат PEM), а не keystore.

<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
            maxThreads="150" SSLEnabled="true" >
       <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
       <SSLHostConfig>
       <Certificate certificateKeyFile="conf/privkey.pem"
                    certificateFile="conf/pubkey.crt"
                    certificateChainFile="conf/localhost-rsa-chain.pem"
                    type="RSA" />
    </SSLHostConfig>
</Connector>

Сертификат публичного ключа как обычно получаем в каком-нибудь центре  сертификации.
Поле certificateChainFile отсутствует, если нет цепочки сертификации.

Для случая самоподписанного сертификата я его сам генерирую:

$ openssl req -new -x509 -days 3660 -newkey rsa:2048 -keyout privkey.pem -out pubkey.crt \
   -subj "/C=RU/ST=Kamchatka/L=Elizovo/O=Ivanov\ A.V./OU=IT/CN=www.arccomm.ru/emailAddress=arcadyivanov@gmail.com"

$ privkey.pem privkey.pem.orig
$ openssl rsa -in privkey.pem.orig -out privkey.pem

 

В Ubuntu 18.04 дополнительно делаю следующее:

# apt-get install libtcnative-1 libapr1
# cd /usr/lib64
# ln -s /usr/lib/x86_64-linux-gnu/libapr-1.so.0    libapr-1.so.0
# ln -s /usr/lib/x86_64-linux-gnu/libtcnative-1.so libtcnative-1.so

 

Вместо символьных линков можно указать местоположение библиотек при запуске Java в catalina.sh:

JAVA_OPTS="$JAVA_OPTS -Djava.library.path=/usr/lib/x86_64-linux-gnu/"