Здравствуйте!
Безопасности в современной деловой информационной среде отводится одна из первостепенных ролей. Нужно ли говорить, что в Банке, где большая часть продуктов строится на Digital-технологиях, а на кону доверие, спокойствие и благосостояние клиентов, информационная безопасность ставится в авангард.
В этой статье расскажу, как мы в своей деятельности применяем один из инструментов обеспечения информационной безопасности.
Аутентификация – это процесс установления подлинности субъекта по его уникальному идентификатору и проверка права на доступ к информационной системе.
Двусторонняя аутентификация – это обоюдная проверка и установление подлинности, при которой информационная система также обязана подтвердить свою аутентичность перед пользователем.
Сервер Red Hat JBoss Enterprise Application Platform является одной из распространенных и популярных платформ для построения корпоративных приложений на Java.
Для настройки возьмем финальный релиз JBoss EAP 7.0.0 от 10.05.2016 из свободного источника: Red Hat JBoss Enterprise Application Platform
При наличии регистрации Red Hat можно скачать дополнительные патчи: Red Hat Customer Portal и установить через консоль своего экземпляра JBoss EAP (http://localhost:9990/console/App.html#patching)
Постановка задачи
Для корпоративного приложения с тонким клиентом в веб-браузере, middle или backend servlet-а, которые запускаются на JBoss EAP, необходимо настроить двустороннюю аутентификацию по протоколу TLSv1.2 с применением зарубежных криптографических алгоритмов.
Задача усложняется тем, что приложение на сервере JBoss в свою очередь взаимодействует, например, с Программно-аппаратным комплексом «ПАК Удостоверяющий центр КриптоПро», где требуется процедура двусторонней аутентификации по протоколу Transport Layer Security (TLSv1.0) c использованием российских криптографических алгоритмов.
Получается, что в одном приложении на сервере JBoss нужно подружить отечественную и зарубежную криптографию.
Аутентификация должна выполняться комбинированным методом инструментами Инфраструктуры открытых ключей (PKI), т.е. с помощью сертификатов клиента и сервера.
Подготовка RSA-ключей аутентификации
Для аутентификации клиента и подтверждения клиенту подлинности самого сервера обеим сторонам необходимо иметь свои пары ключей с сертификатами открытого ключа. А также хранилище корневых доверенных сертификатов, куда должны быть установлены корневые сертификаты удостоверяющих центров, выпустивших конечные сертификаты аутентификации.
Удостоверяющие центры для сервера и клиента могут быть разные.
Пользователь должен доверять удостоверяющему центру, выпустившему сертификат для сервера.
Сервер должен доверять удостоверяющему центру, выпустившему сертификат для клиента.
Существует много утилит и инструментов с хорошим описанием и примерами, которые позволяют работать с хранилищами и выполнять генерацию ключей и запросов на сертификат (keytool, openSSL, Portecle, XCA). Поэтому процесс подготовки хранилищ и выпуск сертификатов здесь пропустим.
Вот, например, хорошая статья и пошаговая инструкция по настройке Two-way SSL with TLS1.2 для Oracle SOA Suite с использованием openSSL и keytool. Но, правда, она частично потеряла актуальность и перестала отражать всю картину. Выпущенные по ней сертификаты не будут корректно восприниматься в веб-браузерах. Так, например, для браузера Google Chrome версии 58 и далее для проверки доменного имени в TLS-сертификате будет использоваться атрибут subjectAlternativeName блока Extensions, а не commonName, как раньше. Теперь браузер выдаст ошибку:
Error: «Subject Alternative Name Missing» or NET::ERR_CERT_COMMON_NAME_INVALID or «Your connection is not private»
если получит с сервера SSL-сертификат без домена в альтернативном имени субъекта сертификации.
Вот что говорит об этом Support Google Chrome Answer
Как с помощью openSSL добавить в сертификат subjectAlternativeName — можно посмотреть здесь и здесь
Разумеется, такой способ получения TLS-сертификатов годится только для внутрикорпоративных приложений.
Для взаимодействия с внешними клиентами нужно обращаться в доверенные центры сертификации, корневые сертификаты которых предустановлены в браузеры, JRE, операционные системы и устройства клиентов. Обзор удостоверяющих центров sslshopper.com
В результате созданы два хранилища. Файл ssl-keystore.jks с ключами и сертификатом сервера JBoss EAP для аутентификации перед клиентом. И ssl-truststore.jks – с доверенным корневым сертификатом УЦ, выпустившим клиентский сертификат аутентификации.
Подготовка GOST-ключей аутентификации
Процесс обоюдной аутентификации сервера JBoss и КриптоПро УЦ сервера в сущности такой же. Единственное отличие в криптографических алгоритмах.
Серверы обмениваются сертификатами с алгоритмами подписи ГОСТ Р 34.11/34.10-2001 и хэширования подписи ГОСТ Р 34.11-94 вместо применяемых в RSA TLSv1.2 sha256RSA и sha256 соответственно. В качестве корневого сертификата здесь выступает ROOT-сертификат самого ПАК КриптоПро УЦ.
Для генерации пары ключей и выпуска ГОСТ-сертификата можно воспользоваться тестовым удостоверяющим центром КриптоПро cryptopro.ru/solutions/test-ca
Или, если есть, развернутым корпоративным УЦ. При этом на рабочей станции должен быть установлен криптографический провайдер КриптоПро CSP.
После регистрации на персональной странице можно будет создавать запросы, управлять своими сертификатами.
При генерации ключевой пары КриптоПро CSP просит выбрать хранилище. Это может быть реестр MS Windows, eToken или JaCarta, если установлены драйверы и соответствующее окружение eToken PKI Client 5.1 SP1 aladdin-rd.ru/support
Сервер JBoss через Java-интерфейсы, реализованные в КриптоПро JCP и КриптоПро JCSP, может работать со следующими типами хранилищ:
JCP - CryptoPro Java Provider
KeyStore - CertStore
KeyStore - FloppyStore
KeyStore - HDImageStore
KeyStore - MemoryStore
KeyStore - MemoryStore0- MemoryStore9
KeyStore - OCFBase
JCSP - CryptoPro Java CSP Provider
KeyStore – FLASH
KeyStore - HDIMAGE
KeyStore – REGISTRY
KeyStore – ARDS JaCarta
KeyStore – Aladdin Token JC
Чтобы иметь доступ к eToken, в КриптоПро JCP необходимо выполнить шаги, приведенные в инструкции КриптоПро Особенности установки при использовании электронных ключей eToken
В контрольной панели КриптоПро JCP появится вкладка OCF и возможность обращаться к eToken через OCFStore.
В данном примере JBoss EAP будет настраиваться на работу с хранилищем KeyStore – HDImageStore. Это значит, что если ключи и сертификат были сгенерированы в реестре ОС Windows, eToken или JaCarta, то нужно решить задачу по их переносу в формат HDImageStore.
Если ключи находятся в реестре или JaCarta, то используем КриптоПро CSP. Вкладка Сервис «Скопировать»
Выбираем контейнер и выполняем его копирование на eToken.
Далее в контрольной панели КриптоПро JCP появляется возможность в OCFStore просмотреть и скопировать этот контейнер из eToken в нужный формат HDImageStore.
Путь к HDImageStore в КриптоПро JCP настраивается на вкладке Hardware
Физически контейнеры с ключами в формате HDImageStore представляют из себя каталоги, в каждом из которых хранится шесть файлов с расширением *.key
В качестве доверенного корневого хранилища с root-сертификатом ПАК КриптоПро УЦ сервер JBoss будет использовать truststore.cpks, подключаемый в КриптоПро JCP на вкладке Keys and certificates stores.
Настройка сервера JBoss
Для настройки сервера JBoss EAP 7.0.0 все изменения нужно внести в конфигурационный файл jboss-EAP-7.0.0\standalone\configuration\standalone.xml
В раздел <system-properties> добавляются настройки, указывающие серверу, каких провайдера и хранилища использовать для двусторонней ГОСТ-аутентификации:
<property name="java.protocol.handler.pkgs" value="javax.net.ssl"/>
<property name="sun.security.ssl.allowUnsafeRenegotiation" value="true"/>
<property name="java.util.logging.ConsoleHandler.level" value="ALL"/>
<property name="javax.net.ssl.keyStoreProvider" value="JCP"/>
<property name="javax.net.ssl.keyStoreType" value="HDImageStore"/>
<property name="javax.net.ssl.keyStorePassword" value="myPass"/>
<property name="javax.net.ssl.trustStoreProvider" value="JCP"/>
<property name="javax.net.ssl.trustStoreType" value="HDImageStore"/>
<property name="javax.net.ssl.trustStorePassword" value="myPass"/>
<property name="javax.net.ssl.trustStore" value="../standalone/configuration/truststore.cpks"/>
<property name="ru.CryptoPro.ssl.SSLLogger.level" value="FINE"/>
<property name="ru.CryptoPro.ssl.SSLLogger.handlers" value="java.util.logging.ConsoleHandler"/>
<property name="ru.CryptoPro.reprov.enableCRLDP" value="true"/>
<property name="com.sun.security.enableCRLDP" value="true"/>
<property name="com.ibm.security.enableCRLDP" value="true"/>
<property name="ocsp.enable" value="false"/>
<property name="com.sun.net.ssl.checkRevocation" value="false"/>
<property name="javax.net.ssl.supportGVO" value="true"/>
Для настройки RSA TLSv1.2 аутентификации пользователя и сервера JBoss в раздел
<management>
<security-realms>
нужно добавить область безопасности:
<security-realm name="SecureRealm">
<server-identities>
<ssl protocol="TLSv1.2">
<keystore path="ssl-keystore.jks" relative-to="jboss.server.config.dir" keystore-password="myPass" alias="my_domain_net"/>
</ssl>
</server-identities>
<authentication>
<truststore path="ssl-truststore.jks" relative-to="jboss.server.config.dir" keystore-password="myPass"/>
</authentication>
</security-realm>
Таким образом, для JBoss указаны все необходимые хранилища ключей и сертификатов.
В раздел
<subsystem xmlns="urn:jboss:domain:undertow:3.1">
<buffer-cache name="default"/>
<server name="default-server">
к уже существующему http-слушателю
<http-listener name="default" max-post-size="104857600" socket-binding="http"/>
нужно добавить https:
<https-listener name="https" verify-client="REQUESTED" security-realm="SecureRealm" socket-binding="https"/>
Атрибут verify-client=«REQUESTED» указывает серверу, что необходимо запросить сертификат пользователя в браузере при обращении к серверу по https-протоколу.
В раздел <socket-binding-group name=«standard-sockets» вносится привязка защищенного сокета:
<socket-binding name="https" port="${jboss.https.port:8443}"/>
В Linux ОС, где порт 8443 особый привилегированный, можно переопределить, например:
<socket-binding name="https" port="${jboss.https.port:19443}"/>
Если необходимо настроить работу приложения JBoss на виртуальном хосте, то в раздел
<subsystem xmlns="urn:jboss:domain:undertow:3.1">
<buffer-cache name="default"/>
<server name="default-server">
требуется добавить рядом с
<host name="default-host" alias="localhost">
настройку своего хоста: <host name="myApp" alias="my.domain.net ">
<location name="/" handler="welcome-content"/>
<filter-ref name="server-header"/>
<filter-ref name="x-powered-by-header"/>
</host>
В раздел
<subsystem xmlns="urn:jboss:domain:security:1.2">
<security-domains>
нужно добавить домен безопасности нашего приложения
<security-domain name="myApp" cache-type="default">
<authentication>
<login-module code="CertificateRoles" flag="required">
<module-option name="verifier" value="org.jboss.security.auth.certs.AnyCertVerifier"/>
<module-option name="securityDomain" value="myApp"/>
<module-option name="rolesProperties" value="file:/JBOSS/jboss-eap-7.0/standalone/configuration/apps/myApp/roles.properties"/>
</login-module>
</authentication>
<jsse keystore-password="myPass" keystore-url="file:/JBOSS/jboss-eap-7.0/standalone/configuration/ssl-keystore.jks" truststore-password="myPass" truststore-url="file:/JBOSS/jboss-eap-7.0/standalone/configuration/ssl-truststore.jks" client-auth="true"/>
</security-domain>
Для авторизации пользователей, при необходимости, как показано в примере, в security-домене можно настроить login-module, который будет определять роль пользователя в приложении по его сертификату аутентификации. Для этого логин-модуль использует подготовленный файл /roles.properties.
Пример содержимого файла – Subject X500Principal = role
#
# user=role1,role2,...
#
C\=RU,\ L\=Moscow,\ O\=Alfabank,\ OU\=ORRPP,\ CN\=Vasiliy\ Burmistrov=admin
C\=RU,\ L\=Moscow,\ O\=Alfabank,\ OU\=ORRPP,\ CN\=Ivan\ Petrov=user
В составе поставки КриптоПро JCP есть инструкция по настройке сервера JBoss jcp-2.0.39014\Doc\WebServerIntegration\JBoss\ Howto_set_gost_tls_jboss.docx
ИНТЕГРАЦИЯ JBOSS 6.1.0 Final (“Neo”) и JTLS 2.0. В этом документе речь идет об устаревшей версии JBoss и процессе аутентификации пользователя по протоколу Gost TLS непосредственно на сервере. Это может быть полезно, если стоит цель поднять защищенный по ГОСТ канал между браузером пользователя или иным фронтом и сервером приложений.
Настройка приложения
Для того чтобы при установке deployment-приложение интегрировалось с настройками на сервере, необходимо внести ряд параметров в его конфигурационные файлы.
Домен безопасности и виртуальный хост приложения указываются в /WEB-INF/jboss-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 5.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_5_0.dtd">
<jboss-web>
<security-domain>myApp</security-domain>
<virtual-host>myApp</virtual-host>
</jboss-web>
В файле /WEB-INF/web.xml настраиваются ограничения безопасности:
<security-constraint>
<web-resource-collection>
<web-resource-name>myApp</web-resource-name>
<url-pattern>/ui1/*</url-pattern>
<url-pattern>/ui2/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
и способ аутентификации:
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>myApp</realm-name>
</login-config>
В результате этих настроек в приложении на сервере JBoss в Runtime можно получать из запроса веб-браузера или иной фронт-системы сертификат прошедшего аутентификацию пользователя:
X509Certificate[] certChain = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
X509Certificate userCert = certChain[0];
Взаимодействуя с ПАК КриптоПро УЦ, приложение использует SOAP API, описание которого приведено в документации ЖТЯИ.00067-02 20 01-ЛУ (ЖТЯИ.00067-02 90 16) «КриптоПро УЦ программно-аппаратный комплекс удостоверяющий центр Руководство программиста»
Веб-сервис вызывается с помощью каркаса Apache Axis 1.4 – реализации интерфейса JAX-WS.
Если в хранилище HDImageStore, кроме ключей аутентификации непосредственно в КриптоПро УЦ, хранятся еще ключи для других приложений и иных целей, то возникает проблема. Apache Axis при вызове сервиса КриптоПро УЦ хватает из хранилища первый попавшийся и, соответственно, аутентификацию не проходит.
Чтобы решить данную проблему, потребовалось сделать свою реализацию SocketFactoryImpl.
Для этого нужно наследоваться от org.apache.axis.components.net.JSSESocketFactory
И реализовать интерфейс org.apache.axis.components.net.SecureSocketFactory
Код собственной фабрики SocketFactoryImpl
package ru.alfabank.orrpp.common.alfaca.ws;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import java.util.logging.Logger;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.apache.axis.components.net.JSSESocketFactory;
import org.apache.axis.components.net.SecureSocketFactory;
/**
* SSL socket factory.
*/
public class SocketFactoryImpl extends JSSESocketFactory implements SecureSocketFactory {
public SocketFactoryImpl(Hashtable attributes) {
super(attributes);
}
private static final Logger LOGGER = Logger.getLogger(SocketFactoryImpl.class.getName());
private static final String SIGNER_KEY_ALIAS = "signer_CA";
private static final String TRUST_CERT_ALIAS = "trust_CA";
private static final char[] PASSWORD = "myPass".toCharArray();
/**
* Read the keystore, init the SSL socket factory
*
* @throws IOException
*/
protected void initFactory() throws IOException {
try {
//Configuration specified in wsdd.
SSLContext context = getContext();
sslFactory = context.getSocketFactory();
} catch (Exception e) {
if (e instanceof IOException) {
throw (IOException) e;
}
throw new IOException(e.getMessage());
}
}
/**
* gets a SSL Context
*
* @return SSLContext
* @throws Exception
*/
protected SSLContext getContext() throws Exception {
System.setProperty("ssl.KeyManagerFactory.algorithm", "GostX509");
System.setProperty("ssl.TrustManagerFactory.algorithm", "GostX509");
System.setProperty("ssl.SocketFactory.provider", "ru.CryptoPro.ssl.SSLSocketFactoryImpl");
System.setProperty("ssl.ServerSocketFactory.provider", "ru.CryptoPro.ssl.SSLServerSocketFactoryImpl");
System.setProperty("ru.CryptoPro.ssl.Provider", "JCP");
System.out.println("javax.net.ssl.trustStore: " + System.getProperty("javax.net.ssl.trustStore"));
KeyStore trustStore = KeyStore.getInstance(System.getProperty("javax.net.ssl.trustStoreType"));
String trustStoreFileName = System.getProperty("javax.net.ssl.trustStore");
trustStore.load(trustStoreFileName == null ? null : new FileInputStream(trustStoreFileName), System.getProperty("javax.net.ssl.trustStorePassword").toCharArray());
KeyStore ks = KeyStore.getInstance(System.getProperty("javax.net.ssl.keyStoreType"));
String keystoreFileName = System.getProperty("javax.net.ssl.keyStore");
ks.load(keystoreFileName == null ? null : new FileInputStream(keystoreFileName), System.getProperty("javax.net.ssl.keyStorePassword").toCharArray());
X509Certificate cert = (X509Certificate) ks.getCertificate(SIGNER_KEY_ALIAS);
LOGGER.info("Signer found: " + ((X509Certificate) cert).getSubjectX500Principal().getName());
PrivateKey privateKey = (PrivateKey) ks.getKey(SIGNER_KEY_ALIAS, PASSWORD);
X509Certificate trustCert = (X509Certificate) trustStore.getCertificate(TRUST_CERT_ALIAS);
LOGGER.info("Trust found: " + ((X509Certificate) trustCert).getSubjectX500Principal().getName());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("GostX509");
tmf.init(trustStore);
SSLContext context = SSLContext.getInstance("GostTLS", "JTLS");
context.init(new KeyManager[] {new KeyManagerImpl(SIGNER_KEY_ALIAS, privateKey, new X509Certificate[] {cert})}, tmf.getTrustManagers(), null);
return context;
}
}
Чтобы заставить Apache Axis использовать собственную фабрику, в @Stateless бине,
где конструируем вызов сервиса КриптоПро УЦ, переопределяем системное свойство:
AxisProperties.setProperty("axis.socketSecureFactory", "ru.alfabank.orrpp.common.alfaca.ws.SocketFactoryImpl");
Настройка JRE
Казалось бы, все настроено, и должно работать. Но нет. Остается еще один важный штрих.
Чтобы из приложения на JBoss аутентифицироваться в КриптоПро УЦ по протоколу Gost TLS, сервер JBoss нужно запускать в JRE с установленным КриптоПро JCP провайдером.
При установке JCP в настройки JRE вносятся изменения. В частности, в jre1.8.0_144\lib\security\ java.security алгоритмы KeyManagerFactory и TrustManagerFactory меняются на Gost. Если так оставить, то в JBoss при старте будет возникать ошибка:
2017-10-12 17:44:13,376 ERROR [org.jboss.msc.service.fail] (MSC service thread 1-7) MSC000001: Failed to start service jboss.security.security-domain.myApp: org.jboss.msc.service.StartException in service jboss.security.security-domain.myApp: WFLYSEC0012: Unable to start the SecurityDomainService service
at org.jboss.as.security.service.SecurityDomainService.start(SecurityDomainService.java:105)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:2032)
at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1955)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
Caused by: java.security.KeyStoreException: Default store provider (keyStore) is SUN but default config provider (cpSSL) is JCP. Check settings on the tab 'Algorithms' of JCP Pane.
at ru.CryptoPro.ssl.r.(Unknown Source)
at ru.CryptoPro.ssl.KeyManagerFactoryImpl.engineInit(Unknown Source)
at javax.net.ssl.KeyManagerFactory.init(Unknown Source)
at org.jboss.security.JBossJSSESecurityDomain.loadKeyAndTrustStore(JBossJSSESecurityDomain.java:488)
at org.jboss.security.JBossJSSESecurityDomain.reloadKeyAndTrustStore(JBossJSSESecurityDomain.java:335)
at org.jboss.as.security.service.SecurityDomainService.start(SecurityDomainService.java:102)
... 5 more
Для исправления комментируем в файле java.security GostX509
#ssl.KeyManagerFactory.algorithm=GostX509
#ssl.TrustManagerFactory.algorithm=GostX509
и возвращаем настройки по умолчанию SunX509
ssl.KeyManagerFactory.algorithm=SunX509
ssl.TrustManagerFactory.algorithm=PKIX
Результаты
Приложение и среда исполнения с двусторонней аутентификацией настроены, каналы передачи данных зашифрованы, между участниками системы установлены надежные соединения.
Полезные ссылки
- Где скачать JBoss EAP 7.0.0
- Red Hat Customer Portal
- Статья Two-way SSL with TLS1.2 для Oracle SOA Suite
- Поддержка Google о «Subject Alternative Name Missing»
- Как с помощью openSSL добавить в сертификат subjectAlternativeName здесь и здесь
- Обзор удостоверяющих центров с зарубежными криптографическими алгоритмами
- Тестовый удостоверяющий центр КриптоПро
- Компания Аладдин, где скачать драйверы поддержки eToken или JaCarta (eToken PKI Client 5.1 SP1)
- Инструкция по настройке КриптоПро JCP для поддержки eToken
mihmig
К чему все эти громкие слова о шифровании, если HDImageStore — это просто папка с файлами (закрытыми ключами шифрования)? Т.е. любой пользователь на сервере может их скопировать.
vburmistrov Автор
mihmig, HDImageStore — это нативное хранилище СКЗИ КриптоПро, оно зашифровано и защищено паролем. Конечно, его надежность сильно проигрывает HSM модулям, тем более с неизвлекаемыми ключами, например, JaСarta.
А по поводу того, что любой пользователь может их скопировать с сервера. Так, во-первых, хранилище JKS или cacerts это тоже файлы. А во-вторых для защиты серверов обычно применяются организационные и технические меры защиты.