Статьи

Как легко настроить взаимный TLS

Мастеринг Двусторонней TLS

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

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

Это следующие шаги:

  1. Запуск сервера
  2. Передавай привет серверу (без шифрования)
  3. Включение HTTPS на сервере (односторонний TLS)
  4. Требовать, чтобы клиент идентифицировал себя (двусторонний TLS)
  5. Двусторонний TLS, основанный на доверии Центру сертификации
  6. Автоматический скрипт для включения аутентификации по TLS

Определение

  • Идентичность: хранилище ключей, которое содержит пару ключей, также известную как закрытый и открытый ключ
  • TrustStore: хранилище ключей, содержащее один или несколько сертификатов, также известных как открытый ключ. Этот KeyStore содержит список доверенных сертификатов
  • Односторонняя аутентификация (также известная как односторонний tls, односторонний ssl): соединение Https, когда клиент проверяет сертификат контрагента
  • Двусторонняя аутентификация (также известная как двусторонняя tls, двухсторонняя ssl, взаимная аутентификация): соединение Https, когда клиент, а также другая сторона проверяет сертификат, также известный как взаимная аутентификация

Полезные ссылки

Ниже приведен список поддерживаемых клиентов для Java и Scala с примерами, см. В классе ClientConfig для подробной конфигурации

  • Apache HttpClient
  • JDK HttpClient
  • Старый JDK HttpClient
  • Spring RestTemplate
  • Spring WebFlux WebClient Netty
  • Spring WebFlux WebClient Jetty
  • OkHttp
  • Джерси Клиент
  • Старый Джерси Клиент
  • Google HttpClient
  • Unirest
  • модифицировать
  • надувать
  • Акка Http Клиент

Запуск сервера

Для начала нам понадобится следующее:

  1. Java 11
  2. Maven 3.5.0
  3. Eclipse, Intellij IDEA (или любой другой текстовый редактор, например VIM)
  4. Терминал
  5. Openssl (для пользователей Windows используйте Git Bash )
  6. Клонируйте проект по адресу : https://github.com/Hakky54/mutual-tls

Если вы хотите запустить этот проект с Java 8, вы можете получить более старую версию с помощью команды git ниже. И рекомендуется следовать инструкции для этой конкретной версии, которая доступна на этой странице

git checkout tags/java-8-compatible

Запустите сервер, запустив основной метод класса приложения в проекте сервера или выполнив следующую команду из терминала в корневом каталоге:

cd server/ && mvn spring-boot:run

Передавая привет серверу (без шифрования)

В настоящее время сервер работает по умолчанию порт 8080 без шифрования. Вы можете вызвать конечную точку hello с помощью следующей команды curl в терминале:

curl -i -XGET http://localhost:8080/api/hello

Он должен дать вам следующий ответ:

HTTP/1.1 200
Content-Type: text/plain;charset=UTF-8
Content-Length: 5
Date: Sun, 11 Nov 2018 14:21:50 GMT

Hello

Вы также можете вызвать сервер с указанным клиентом в каталоге клиента. Клиент является интеграционным тестом на основе Cucumber, и вы можете запустить его, запустив класс ClientRunnerIT из вашей IDE или выполнив следующую команду из терминала в корневом каталоге cd client/ && mvn exec:java. Есть файл Hello.feature, который описывает шаги для интеграционного теста. Вы можете найти его в тестовых ресурсах клиентского проекта. Существует еще один способ запустить сервер и клиент, используя следующую команду в корневом каталоге:mvn clean verify, Клиент по умолчанию отправляет запросы на localhost, поскольку ожидает сервер на той же машине. Если сервер работает на другом компьютере, вы все равно можете указать пользовательский URL со следующим аргументом VM при запуске клиента:-Durl=http://[HOST]:[PORT]

Включение HTTPS на сервере (односторонний TLS)

Теперь вы узнаете, как защитить свой сервер, включив TLS. Вы можете сделать это, добавив необходимые свойства в файл свойств приложения с именем:application.yml

Добавьте следующее свойство:

server:
  port: 8443
  ssl:
    enabled: true

Вы, вероятно, спросите себя, почему порт установлен на 8443. Соглашение о портах для сервера Tomcat с https — 8443, а для http — 8080. Таким образом, мы могли бы использовать порт 8080 для соединений https, но это плохая практика. , Посмотрите Википедию для получения дополнительной информации о соглашениях порта.

Перезапустите сервер, чтобы он мог применить сделанные вами изменения. Вы, вероятно , получите следующее исключение: IllegalArgumentException: Resource location must not be null.

Вы получаете это сообщение, потому что серверу требуется хранилище ключей с сертификатом сервера, чтобы обеспечить безопасное соединение с внешним миром. Сервер может предоставить вам больше информации, если вы укажете следующий аргумент VM:-Djavax.net.debug=SSL,keymanager,trustmanager,ssl:handshake

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

Чтобы создать хранилище ключей с открытым и закрытым ключом, выполните в терминале следующую команду:

keytool -genkeypair -keyalg RSA -keysize 2048 -alias server -dname "CN=Hakan,OU=Amsterdam,O=Thunderberry,C=NL" -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -validity 3650 -keystore server/src/main/resources/identity.jks -storepass secret -keypass secret -deststoretype pkcs12

Теперь вам нужно сообщить вашему серверу, где находится хранилище ключей, и предоставить пароли. Вставьте следующее в ваш application.ymlфайл:

server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:identity.jks
    key-password: secret
    key-store-password: secret

Поздравляем! Вы включили TLS-шифрованное соединение между сервером и клиентом! Теперь вы можете попытаться вызвать сервер с помощью следующей команды curl:curl -i --insecure -v -XGET https://localhost:8443/api/hello

Давайте также запустим клиент в классе ClientRunnerIT .

Вы увидите следующее сообщение об ошибке: java.net.ConnectException: Connection refused (Connection refused). Похоже, клиент пытается передать привет серверу, но сервера там нет. Проблема заключается в том, что клиент пытается передать привет серверу через порт 8080, когда он активен через порт 8443. Примените следующие изменения к классу констант:

От :

private static final String DEFAULT_SERVER_URL = "http://localhost:8080";

Кому :

private static final String DEFAULT_SERVER_URL = "https://localhost:8443";

Давайте попробуем снова запустить клиент, и вы увидите, что появится следующее сообщение: «javax.net.ssl.SSLHandshakeException: сбой построения пути PKIX: sun.security.provider.certpath.SunCertPathBuilderException: невозможно найти действительный путь сертификации для запрошенного мишень». Это означает, что клиент хочет установить связь через HTTPS, и во время процедуры квитирования он получил сертификат сервера, который он еще не распознает. Для этого вам также необходимо создать склад доверенных сертификатов. Склад доверенных сертификатов — это чемодан, содержащий доверенные сертификаты. Клиент сравнит сертификат, который он получит в процессе SSL Handshake, с содержимым своего хранилища доверенных сертификатов. В случае совпадения процесс SSL Handshake будет продолжен. Перед созданием доверенных хранилищ необходимо иметь сертификаты сервера.Вы можете получить его с помощью следующей команды:

Экспортный сертификат сервера

keytool -exportcert -keystore server/src/main/resources/identity.jks -storepass secret -alias server -rfc -file server/src/main/resources/server.cer

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

keytool -keystore client/src/test/resources/truststore.jks -importcert -file server/src/main/resources/server.cer -alias server -storepass secret

Вы создали хранилище доверенных сертификатов для клиента. К сожалению, клиент не знает об этом. Теперь вы должны сказать, что ему нужно использовать доверенное хранилище с правильным расположением и паролем. Вы также должны сообщить клиенту, что аутентификация включена. Укажите следующее свойство в application.ymlфайле клиента:

client:
  ssl:
    one-way-authentication-enabled: true
    two-way-authentication-enabled: false
    trust-store: truststore.jks
    trust-store-password: secret

Требовать, чтобы клиент идентифицировал себя (двусторонний TLS)

Следующим шагом является требование аутентификации клиента. Это заставит клиента идентифицировать себя, и, таким образом, сервер также может проверить личность клиента и узнать, является ли он доверенным. Вы можете включить это, сообщив серверу, что вы также хотите проверить клиента с помощью свойства client-auth. Поместите следующие свойства в application.ymlсервер:

server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:identity.jks
    key-password: secret
    key-store-password: secret
    client-auth: need

При запуске клиента, он потерпит неудачу с сообщением об ошибке следующего: javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate. Это указывает на то, что сертификат клиента недействителен, поскольку сертификата вообще нет. Итак, давайте создадим один с помощью следующей команды

keytool -genkeypair -keyalg RSA -keysize 2048 -alias client -dname "CN=Suleyman,OU=Altindag,O=Altindag,C=NL" -validity 3650 -keystore client/src/test/resources/identity.jks -storepass secret -keypass secret -deststoretype pkcs12

Вам также необходимо создать склад доверенных сертификатов для сервера. Перед созданием склада доверенных сертификатов необходимо иметь сертификаты клиента. Вы можете получить его с помощью следующей команды:

Экспортный сертификат клиента

keytool -exportcert -keystore client/src/test/resources/identity.jks -storepass secret -alias client -rfc -file client/src/test/resources/client.cer

Создать доверенное хранилище сервера с сертификатом клиента

keytool -keystore server/src/main/resources/truststore.jks -importcert -file client/src/test/resources/client.cer -alias client -storepass secret

Вы создали дополнительное хранилище ключей для клиента. К сожалению, клиент не знает об этом. Теперь вам нужно сказать, что ему также нужно использовать хранилище ключей с правильным расположением и паролем. Вы также должны сообщить клиенту, что двусторонняя аутентификация включена. Укажите следующее свойство в application.ymlфайле клиента:

client:
  ssl:
    one-way-authentication-enabled: false
    two-way-authentication-enabled: true
    key-store: identity.jks
    key-password: secret
    key-store-password: secret
    trust-store: truststore.jks
    trust-store-password: secret

Сервер также не знает о недавно созданном хранилище доверенных сертификатов. Поэтому замените текущие свойства следующими свойствами:

server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:identity.jks
    key-password: secret
    key-store-password: secret
    trust-store: classpath:truststore.jks
    trust-store-password: secret
    client-auth: need

Если вы снова запустите клиент, вы увидите, что тест пройден и что клиент получил приветственное сообщение от сервера безопасным способом. Поздравляем! Вы закончили установку двусторонней TLS!

Двусторонний TLS, основанный на доверии Центру сертификации

Существует еще один способ взаимной аутентификации, основанный на доверии центру сертификации. Есть плюсы и минусы.

Pros

  • Клиентам не нужно добавлять сертификат сервера
  • Серверу не нужно добавлять все сертификаты клиентов
  • Обслуживание будет меньше, потому что только срок действия сертификата центра сертификации может истечь

Cons

  • Вы больше не можете контролировать, для каких приложений разрешено вызывать ваше приложение. Вы даете разрешение любому приложению, у которого есть сертификат, подписанный центром сертификации.

Это следующие шаги:

  1. Создание центра сертификации
  2. Создание запроса на подпись сертификата
  3. Подписание сертификата с запросом на подписание сертификата
  4. Заменить неподписанный сертификат подписанным
  5. Доверие только Центру сертификации

Создание центра сертификации

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

keytool -genkeypair -keyalg RSA -keysize 2048 -alias root-ca -dname "CN=Root-CA,OU=Certificate Authority,O=Thunderberry,C=NL" -validity 3650 -keystore root-ca/identity.jks -storepass secret -keypass secret -deststoretype pkcs12

Или вы можете использовать тот, который уже предоставлен в хранилище, см. Identity.jks

Создание запроса на подпись сертификата

Чтобы подписать сертификат, необходимо предоставить файл запроса на подпись сертификата (.csr). Это можно создать с помощью следующей команды:

Запрос на подпись сертификата для сервера
keytool -certreq -keystore server/src/main/resources/identity.jks -alias server -keypass secret -storepass secret -keyalg rsa -file server/src/main/resources/server.csr
Запрос на подпись сертификата для клиента
keytool -certreq -keystore client/src/test/resources/identity.jks -alias client -keypass secret -storepass secret -keyalg rsa -file client/src/test/resources/client.csr

Центру сертификации нужны эти файлы CSR, чтобы иметь возможность подписать его. Следующим шагом будет подписание запросов.

Подписание сертификата с запросом на подписание сертификата

До сих пор вы использовали только keytool, но для процедуры подписи вам нужен openssl. Файл csr может быть подписан файлом pem и закрытым ключом центра сертификации. Файл pem — это контейнерный формат, который может включать в себя только файлы открытого сертификата и сертификата CA или может включать в себя всю цепочку сертификатов, включая открытый ключ, закрытый ключ и корневые сертификаты. В этом примере он будет содержать только публичный сертификат. Вы извлечете файл pem и key из identity.jks с помощью следующих команд:

Конвертировать хранилище ключей Java в файл p12
keytool -importkeystore -srckeystore root-ca/identity.jks -destkeystore root-ca/root-ca.p12 -srcstoretype jks -deststoretype pkcs12 -srcstorepass secret -deststorepass secret
Создать файл pem из файла p12
openssl pkcs12 -in root-ca/root-ca.p12 -out root-ca/root-ca.pem -nokeys -passin pass:secret -passout pass:secret
Создать файл ключа из файла p12
openssl pkcs12 -in root-ca/root-ca.p12 -out root-ca/root-ca.key -nocerts -passin pass:secret -passout pass:secret

Следующим шагом будет подписание сертификатов. Вы можете подписать его с помощью следующих команд:

Подписание сертификата клиента
openssl x509 -req -in client/src/test/resources/client.csr -CA root-ca/root-ca.pem -CAkey root-ca/root-ca.key -CAcreateserial -out client/src/test/resources/client-signed.cer -days 1825 -passin pass:secret
Подписание сертификата сервера
openssl x509 -req -in server/src/main/resources/server.csr -CA root-ca/root-ca.pem -CAkey root-ca/root-ca.key -CAcreateserial -out server/src/main/resources/server-signed.cer -sha256 -extfile server/src/main/resources/extensions/v3.ext -days 1825 -passin pass:secret

Заменить неподписанный сертификат подписанным

Хранилище ключей идентификации сервера и клиента все еще имеет неподписанный сертификат. Теперь вы можете заменить его на подписанный. Это будет не так просто, потому что подписанный сертификат не имеет закрытого ключа, поэтому без него он не будет действительным. Что вам нужно сделать, это извлечь файл ключа из удостоверения, а затем объединить его обратно в удостоверение с подписанным сертификатом. Вы можете сделать это с помощью следующих команд:

клиент

keytool -importkeystore -srckeystore client/src/test/resources/identity.jks -destkeystore client/src/test/resources/client.p12 -srcstoretype jks -deststoretype pkcs12 -srcstorepass secret -deststorepass secret
openssl pkcs12 -in client/src/test/resources/client.p12 -nodes -out client/src/test/resources/client-private.key -nocerts -passin pass:secret
openssl pkcs12 -export -in client/src/test/resources/client-signed.cer -inkey client/src/test/resources/client-private.key -out client/src/test/resources/client-signed.p12 -name client -passout pass:secret
keytool -delete -alias client -keystore client/src/test/resources/identity.jks -storepass secret
keytool -importkeystore -srckeystore client/src/test/resources/client-signed.p12 -srcstoretype PKCS12 -destkeystore client/src/test/resources/identity.jks -srcstorepass secret -deststorepass secret

сервер

keytool -importkeystore -srckeystore server/src/main/resources/identity.jks -destkeystore server/src/main/resources/server.p12 -srcstoretype jks -deststoretype pkcs12 -srcstorepass secret -deststorepass secret
openssl pkcs12 -in server/src/main/resources/server.p12 -nodes -out server/src/main/resources/server-private.key -nocerts -passin pass:secret
openssl pkcs12 -export -in server/src/main/resources/server-signed.cer -inkey server/src/main/resources/server-private.key -out server/src/main/resources/server-signed.p12 -name server -passout pass:secret
keytool -delete -alias server -keystore server/src/main/resources/identity.jks -storepass secret
keytool -importkeystore -srckeystore server/src/main/resources/server-signed.p12 -srcstoretype PKCS12 -destkeystore server/src/main/resources/identity.jks -srcstorepass secret -deststorepass secret

Доверие только Центру сертификации

Теперь вам нужно настроить свой клиент и сервер так, чтобы они доверяли только центру сертификации. Вы можете сделать это, импортировав сертификат центра сертификации в доверенные хранилища клиента и сервера. Вы можете сделать это с помощью следующих двух команд:

клиент

keytool -keystore client/src/test/resources/truststore.jks -importcert -file root-ca/root-ca.pem -alias root-ca -storepass secret

сервер

keytool -keystore server/src/main/resources/truststore.jks -importcert -file root-ca/root-ca.pem -alias root-ca -storepass secret

В доверенных хранилищах по-прежнему содержатся клиентские и серверные сертификаты, которые необходимо удалить. Вы можете сделать это с помощью следующей команды:

клиент

keytool -keystore client/src/test/resources/truststore.jks -delete -alias server -storepass secret

сервер

keytool -keystore server/src/main/resources/truststore.jks -delete -alias client -storepass secret

Если вы снова запустите клиент, вы увидите, что тест пройден и клиент получил приветственное сообщение от сервера, основываясь на сертификате, который подписан центром сертификации.

Автоматический скрипт для включения аутентификации по TLS

Вы также можете автоматизировать все предыдущие шаги, описанные выше, с помощью предоставленных сценариев в каталоге сценариев этого проекта. Запустите одну из следующих команд для запуска сценариев:

  • Односторонняя аутентификация: ./configure-one-way-authentication
  • Двусторонняя аутентификация: ./configure-two-way-authentication-by-trusting-each-other my-company-name
  • Двусторонняя аутентификация, доверяя центру сертификации: ./configure-two-way-authentication-by-trusting-root-ca my-company-name