Привет, Хабр! В современном мире абсолютное большинство сайтов используют HTTPS (Google даже снижает рейтинг сайтов работающих по HTTP в поисковой выдаче), а подключение к различным системам происходит по протоколу TLS/SSL. Поэтому любой разработчик рано или поздно сталкивается с этими технологиями на практике. Данная статья призвана помочь разобраться, если вы совершенно не в курсе что это такое и как оно устроено. Мы разберем как работает соединение по протоколу TLS, как выпустить собственные сертификаты и настроем TLS в Spring Boot приложении. Поехали!

Что не так с HTTP?

Проблема протокола HTTP в том, что данные передаются по сети в открытом незашифрованном виде. Это позволяет злоумышленнику слушать передаваемые пакеты и извлекать любую информацию из параметров, заголовков и тела сообщения. Для устранения уязвимости был разработан HTTPS (S в конце значит Secure) - он, хоть не является отдельным протоколом, всего лишь HTTP поверх SSL (а позже TLS), позволяет безопасно обмениваться данными. В отличие от HTTP со стандартным TCP/IP портом 80, для HTTPS используется порт 443.

SSL

Secure Sockets Layer (SSL) - это криптографический протокол, обеспечивающий безопасное общение пользователя и сервера по небезопасной сети. Располагается между транспортным уровнем и уровнем программы-клиента (FTP, HTTP и т.п.) (подробнее про уровни телекоммуникаций). Впервые был представлен публике в 1995 году, однако с 2015 года признан полностью устаревшим. На основе спецификации SSL 3.0 в 1996 был разработан TLS 1.0.

TLS

И так, что же такое TLS? Transport Layer Security - это развитие идей, заложенных в протоколе SSL. На данный момент актуальной является версия TLSv1.2, с августа 2018 активно вводится TLSv1.3, тогда как TLSv1.1, TLSv1.0, SSLv3.0, SSLv2.0, SSLv1.0 находятся в статусе deprecated. Протокол обеспечивает услуги: приватности (сокрытие передаваемой информации), целостности (обнаружение изменений), аутентификации (проверка авторства). Достигаются они за счет гибридного шифрования, то есть совместного использования ассиметричного и симметричного шифрования.

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

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

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

Что происходит на практике

  1. Client Hello - клиент начинает общение с сервером отсылая информацию о предпочитаемой версии протокола TLS, набора поддерживаемых шифров (Cipher Spec), и случайного простого числа (client random), необходимого в дальнейшем для генерации общего ключа симметричного шифрования.

    Что такое Cipher Spec? В процессе установки соединения, клиент и сервер должны договориться о: какой алгоритм использовать для обмена ключами (например, RSA - Риверт-Шамир-Адлеман, DH - Диффи-Хеллмана, ECDH - Диффи-Хеллмана на эллиптических кривых, и др.), какой алгоритм использовать для шифрования данных (AES - Advanced Encryption Standard, 3DES - Tripple Data Encryption Algorithm, и др.), какую криптографическую хэш-функцию использовать для генерации Message Authentication Code (SHA-256, SHA-384, SHA-512 - Secure Hash Algorithm с соответствующей длиной строки в битах с хэшем, и др.).

    Что такое Message Authentication Code или MAC? Это хэш, сгенерированный с использованием выбранной криптографической хэш-функции и разделяемого ключа, который добавляется сзади к сообщению. Перед отправкой данных отправитель вычисляет MAC для них, а получатель перед обработкой вычисляет MAC для принятого сообщения и сравнивает его с MAC этого принятого сообщения. Предназначен для проверки целостности, то есть что сообщение не было изменено при его передаче.

  2. Server Hello - сервер отвечает выбранной версией протокола и выбранным из предложенного набора шифром, которые будут непосредственно использоваться, своим случайным простым числом (server random) и идентификатором сессии.

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

  3. Certificate - сервер отправляет свой сертификат, а клиент производит проверку подписи удостоверяющего центра, проверку доверия к удостоверяющему центру, проверку указанного домена сайта с фактическим, срока действия, проверяет не был ли сертификат отозван.

    Что представляет из себя сертификат? Сертификат - это открытый ключ и другая информация о его владельце, а также Электронная Цифровая Подпись (ЭЦП) доверенного центра.

    Как работает ЭЦП? При создании ЭЦП хэш данных, которые подписываются, шифруется закрытым ключом, в отличие от обычного ассиметричного шифрования, где зашифровка выполняется открытым ключом. Таким образом, если вам удалось расшифровать открытым ключом хэш, и он оказался идентичен хэшу из данных, - вы можете быть уверены что: подпись была сделана именно владельцем приватного ключа, открытый ключ которого вы используете; данные, которые были подписаны, не изменились с момента подписания.

    Но как удостовериться, что открытый ключ принадлежит не злоумышленнику? Существуют корневые удостоверяющие центры (Root Certificate Authority или просто CA - Certificate Authority), которым доверяют все участники обмена информацией. Если в цепочке подписания сертификата сервера есть подпись корневого CA (мы можем проверить ее с помощью открытого ключа CA), то мы можем ему доверять. При этом сертификаты (открытые ключи) корневых CA распространяются посредством включения их в операционную систему или браузер поставщиками. Также стоит отметить, что сертификат может быть подписан сертификатом, который подписан в свою очередь другим сертификатом - это цепочка подписания.

    Кем подписан сертификат корневого CA? А никем, нет инстанции выше корневого CA. Сертификат (открытый ключ) в этом случае подписан собственным закрытым ключом. Такие сертификаты называют самоподписанные (sefl-signed).

  4. Server Key Exchange - этот этап происходит не всегда, только если необходимы дополнительные данные для создания симметричного ключа при выбранном алгоритме. Например, при обмене ключами RSA этот шаг пропускается и для обмена общим ключ передается от клиента серверу зашифрованным открытым ключом сервера из его сертификата. Однако в этой статье рассмотрим более надежный алгоритм Диффи-Хеллмана. Сервер отправляет числа p (большое простое число) и g (может быть маленьким), а также рассчитанное число Ys=gслучайно выбранное сервером числоmod p, где mod - это операция нахождения остатка от деления. В свою очередь клиент также рассчитывает Yc=gслучайно выбранное клиентом числоmod p. После этого сервер считает Ycслучайно выбранное сервером числоmod p, а клиент Ysслучайно выбранное клиентом числоmod p, в результате чего у клиента и сервера получается одинаковое число. Разберем на примере:

    • Сервер случайно генерирует число 6, передает клиенту числа p = 17, g = 3, Ys = 36mod 17 = 15

    • Клиент случайно генерирует число 7 и возвращает серверу Yc = 37mod 17 = 11

    • Сервер считает итоговое число 116mod 17= 8, и клиент 157mod 17 = 8

  5. Server Hello Done - сервер сообщает, что начальный этап установки соединения завершен

  6. Client Key Exchange - как было уже сказано выше, когда сервер передал числа p, g, Ys в Server Key Echange, клиент передает свое число Yc в Client Key Exchange. Вычисленное в конце общее одинаковое число используется для создания pre-master secret - предварительного разделяемого ключа. На основании client random, server random и pre-master secret псевдослучайная функция выдает симметричный ключ и ключ вычисления MAC. Таким образом клиент и сервер имеют все необходимое для начала обмена полезной информацией.

  7. Change Cipher Spec - клиент говорит серверу, что он готов перейти на защищенное соединение.

  8. Finished - клиент зашифровывает симметричным ключом первое сообщение с MAC.

  9. Change Cipher Spec - сервер проверяет сообщение Finished от клиента и отправляет в ответ свою готовность к защищенному соединению.

  10. Finished - аналогично клиенту, сервер отправляет тестовое зашифрованное сообщение

  11. После этого соединение считается установленным, и происходит передача полезной информации

  12. close_notify - служебное сообщение, которое одна сторона отправляет другой, как уведомление о том, что считает соединение разорванным и не будет принимать больше сообщения. Другая сторона в ответ обязана послать аналогичное сообщение close_notify.

Двусторонний TLS

Двусторонний TLS или Two Way TLS или mutual TLS (mTLS) означает проверку сертификата клиента. Сервер после своего сообщения Certificate посылает запрос сертификата клиента CertificateRequest. Клиент в ответ отправляет Certificate, сервер производит проверку, аналогичную проверке сертификата сервера клиентом. Далее настройка TLS происходит в описанном выше порядке.

TLSv1.3

Стоит отметить, что все выше написанное относится к TLSv1.2, которая начинает понемногу устаревать. В 2018 году была разработана новая версия 1.3 в которой: были запрещены уже ненадежные алгоритмы, ускорен процесс соединения, переработан протокол рукопожатия и др. Интернет медленно но верно обновляется до TLSv1.3, однако все еще большинство сайтов работают по протоколу TLSv1.2. Поэтому информация в этой статье остается актуальной.

Выпускаем собственные сертификаты

Теперь, когда мы разобрали теорию, самое время приступить к практике! Нам понадобятся OpenSSL и keytool (входит в поставку JDK). Для начала создадим сертификат корневого CA, которым будем подписывать запросы на подпись сертификата клиента и сервера. Сгенерируем приватный ключ RSA зашифрованный AES 256 с паролем "password" длиной 4096 бит (меньше 1024 считается ненадежным) в файл CA-private-key.key:

openssl genrsa -aes256 -passout pass:password -out CA-private-key.key 4096

Нет какого-то принятого стандарта расширений для файлов, связанных с сертификатами. Мы будем использовать:

  • .key - для приватного ключа

  • .csr - для запроса на подпись сертификата

  • .pem - для сертификата в Privacy Enchanced Mail формате. Записывается в base64 между -----BEGIN CERTIFICATE----- и -----END CERTIFICATE-----. Также существует Distinguished Encoding Rules (DER) формат, где информация хранится как binary.

  • p12 - для хранилища ключей с сертификатами (keystore) и хранилища доверенных сертификатов (truststore) в формате Public-Key Cryptography Standards 12.

Далее создадим новый запрос на подпись сертификата CA-certificate-signing-request.csr, передавая информацию о субъекте "CN=Certificate authority" (если не указывать ключ -subj вас попросят указать: Сountry (C), Locality (L), Organisation (O), Organisation Unit (OU), Common Name (CN), Email, Challenge password - все поля, кроме CN опциональны), приватный ключ и пароль от него:

openssl req -new -key CA-private-key.key -passin pass:password -subj "/CN=Certificate authority/" -out CA-certificate-signing-request.csr t $3

Так как подписать сертификат другим сертификатом пока нельзя, подпишем запрос его же приватным ключом. Получившейся сертификат CA-self-signed-certificate.pem будет самоподписанным со сроком действия 1 день.

openssl x509 -req -in CA-certificate-signing-request.csr -signkey CA-private-key.key -passin pass:password -days 1 -out CA-self-signed-certificate.pempemE

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

openssl genrsa -aes256 -passout pass:password -out Server-private-key.key 4096
openssl req -new -key Server-private-key.key -passin pass:password -subj "/CN=localhost/" -out Server-certificate-signing-request.csrt $3

openssl genrsa -aes256 -passout pass:password -out Client-private-key.key 4096
openssl req -new -key Client-private-key.key -passin pass:password -subj "/CN=Client/" -out Client-certificate-signing-request.csr

Подпишем запросы нашим сертификатом CA. Ключ CAcreateserial отвечает за создание файла (в данном случае CA-self-signed-certificate.srl) , в котором будет храниться серийный номер для следующего подписываемого этим сертификатом запроса. Серийный номер для текущего же сертификата сгенерируется случайно.

openssl x509 -req -in Server-certificate-signing-request.csr -CA CA-self-signed-certificate.pem -CAkey CA-private-key.key -passin pass:password -CAcreateserial -days 1 -out Server-certificate.pemt $4
openssl x509 -req -in Client-certificate-signing-request.csr -CA CA-self-signed-certificate.pem -CAkey CA-private-key.key -passin pass:password -days 1 -out Client-certificate.pem

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

openssl pkcs12 -export -in Server-certificate.pem -inkey Server-private-key.key -passin pass:password -passout pass:password -out Server-keystore.p12      

Осталось только создать хранилище доверенных сертификатов (truststore): сервер будет доверять всем клиентам, в цепочке подписания которых есть сертификат из truststore. К сожалению, для Java сертификаты в truststore должны содержать специальный object identifier, а OpenSSL пока не поддерживает их добавление. Поэтому здесь мы прибегнем к поставляемому вместе с JDK keytool:

keytool -import -file CA-self-signed-certificate.pem -keystore Server-truststore.p12 -storetype PKCS12 -storepass password -noprompt    

Для удобства, все описанные выше действия упакованы в bash script.

Настройка TLS в Spring Boot приложении

Основой для нашего проекта послужит шаблон с https://start.spring.io/ с одной лишь зависимостью Spring Web. Для включения TLS указываем в application.properties:

server.port=443
server.ssl.enabled=true
server.ssl.protocol=TLS
server.ssl.enabled-protocols=TLSv1.2

После этого указываем Spring тип keystore, путь к нему и пароль:

server.ssl.key-store-type=PKCS12
server.ssl.key-store=Server-keystore.p12
server.ssl.key-store-password=password

Для проверки доступа создадим минимальный контроллер:

@RestController
public class TlsController {

    @GetMapping
    public String helloWorld() {
        return "Hello, world!";
    }
}

Запускаем проект. Попробуем сделать запрос с помощью curl:

curl https://localhost/

curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.

Как видим, curl не доверяет сертификату сервера. Сделаем еще один запрос, указав наш CA сертификат в ключе --cacert:

curl --cacert CA-self-signed-certificate.pem https://localhost/

Hello, world!

На этот раз все сработало, TLS в Spring Boot работает! Мы на этом не остановимся, добавим в приложение аутентификацию клиента (указываем truststore):

server.ssl.client-auth=need
server.ssl.trust-store-type=PKCS12
server.ssl.trust-store=Server-truststore.p12
server.ssl.trust-store-password=password

Запускаем и снова пытаемся выполнить запрос:

curl --cacert CA-self-signed-certificate.pem https://localhost/

curl: (35) error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate     

Очевидно, что сервер закрыл соединение, так как curl не предоставил никакого сертификата. Дополним запрос клиентским сертификатом и его закрытым ключом:

curl --cacert CA-self-signed-certificate.pem --cert Client-certificate.pem:password --key Client-private-key.key https://localhost/     

Hello, world!

Итоги

В данной статье мы разобрались как работает протокол TLS и для чего он нужен. На практике научились создавать собственные сертификаты и использовать их в Java приложении на Spring Boot. Надеюсь, представленная информация оказалась Вам полезной. Спасибо за внимание!

Комментарии (11)


  1. desertkun
    05.12.2021 17:00
    +4

    Сам процесс TLS хендшейка полуинтерактивный сайт-пример tls.ulfheim.net описывает намного лучше. Вся эта информация и так доступна и давно разжевана, в этой статье нет исследования.


  1. ky0
    05.12.2021 17:37
    +12

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

    А то понастраивают в своих спрингбутах непойми какие шифры, сертификатов самоподписанных навыпускают со строками протухания 10 лет, кейсторов наплодят — разгребай потом за вами… *ворчание*


    1. k3NGuru
      05.12.2021 19:15

      А есть какая-нибудь статья или заметка как это сделать? Я про http-гейте, где стоит какой-нибудь нгинкс и certbot, обновляющий своевременно сертификаты.

      У нас тоже спринг и тоже кейсторы :)


      1. ky0
        05.12.2021 19:21
        +1

        Я не уверен, что подобные мануалы достойны отдельных статей (хотя в интернете наверняка их полно). Такие вещи должен уметь, имхо, даже самый зелёный линуксовый админ.

        Чего там делать-то, в общем? Разворачиваете контейнер/виртуалку, заруливаете на неё 80 и 443 порты снаружи, apt install nginx certbot, выпускаете сертификаты, настраиваете нужные домены в нгинксе с проксированием, куда надо, добавляете в крон/systemd-таймер перевыпуск. Между гейтом и приложениями шифрование необязательно, но в целом, если очень хочется — подойдут и самоподписанные сертификаты.


        1. k3NGuru
          05.12.2021 19:39

          Я то думал, там в связке на самом Spring. А тут все банально.

          Но все равно спасибо.


    1. scruff
      06.12.2021 12:09
      -2

      В чем проблема с сертом в 10лет? Я например всегда ставлю 50-100лет. 1 раз настроил и забыл навсегда. Особо умных юзеров "ой а что уэто хром у меня ругается что небезопасное соединение" - прошу ставить серт в доверенные, а если возмущаются - посылаю в закат. Да и вообще с этим обновлением и перевыпуском сертов один большой головняк. Такое ощущение что это было создано специально чтобы вовремя "заносили" деньгу всяким рапид-ssl-ям и верисайнам. Почему нельзя было поле "истекает" убрать в принципе у сертификатов? Зачем серту обязательно протухать? Ну хочешь ты аннулировать серт - ну закинь его в CRL и делов. Нет блин, надо еще каждый год париться с обновлением сертов, которое мало того что платное, так еще зачастую в зависимости от кривизны написанности сервиса может положить его с чертям основательно и надолго.


      1. ky0
        06.12.2021 13:06
        +5

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

        Уже давно существует бесплатный и удобнейший летсэнкрипт. Преимущества коротких сроков жизни и автоматического выпуска сертификатов уже сто раз на Хабре обсуждены — это существенно снижает площадь атаки и уменьшает вероятность человеческих ошибок.

        Добавление невалидных сертификатов в доверенные — это вообще за гранью добра и зла.


        1. scruff
          07.12.2021 17:00

          Ну тогда и я буду прямо говорить. Вы рассуждаете так, как-будто кроме своего спринбута вы ничего в жизни не видели. Вы вообще в курсе, что в мире полно легаси-софта, который клепали еще в прошлом веке. Что такое поддерживать такого динозавра вы тоже походу не в курсе? И то что приходится админить такое ископаемое зачастую не по своей воле - тоже нет? И что вы не в силе принять управленческое решение, чтобы уйти на что-то новое - нет? Вы рассуждаете крайне узко.

          Лэценкрипт далеко не всегда работает - например если винда. Или опять легаси с проприетарщиной, где любая правка конфы выливается в простой сервиса.

          Сколько на хабре не обитаю - ниразу не видел статей про "опасность" сертов-долгожителей. Будьте так любезны, приведите пару статей в доказательство вашей правоты. Мне будет интересно. Честно. Может я даже поменяю свою точку зрения. Но будь бы у меня выбор между летценкрипт и каким-нибудь другим вендором посолиднее, типа рапида и верисайна - я бы выбрал последний. Меньше мороки, деньгу занести готов.

          Не невалидных, а недоверенных - это разные вещи, у вас в понятиях каша походу, надо теорию подтянуть вам немного. Невалидный серт это тот который протух или SAN не бьётся c FQDN хоста или когда сам серт отозван корневым СА и пусть ему один - в корзину. Недоверенный серт - тут всё норм, дата и FQDN-SAN сходится, зачем паниковать - ставишь в трастед и вот тебе доверенный. Во вторых, многие вендоры до сих пор позволяют ставить нетрастед серты или даже селф-сайнед в трастед-хранилища и даже позволяют автоматизировать этот процесс. Почему? Потому что это суровая необходимость и пользуются ей многие и до сих пор, и еще 100500 лет будут пользоваться. А раз позволяют - значит это безопасно.


          1. ky0
            07.12.2021 17:35

            Вы рассуждаете так, как-будто кроме своего спринбута вы ничего в жизни не видели.

            По правде говоря, вообще нельзя сказать, что я много раз видел «спрингбута» — это уровень приложения, а я по большей части всё-таки инфраструктурщик/сетевик, поэтому в первом комментарии и объясняю автору текста, что, как раз, имплементация шифрования в спрингбуте — плохая идея.

            Вы вообще в курсе, что в мире полно легаси-софта, который клепали еще в прошлом веке. Что такое поддерживать такого динозавра вы тоже походу не в курсе? И то что приходится админить такое ископаемое зачастую не по своей воле — тоже нет?

            Ещё как в курсе, и именно об этом я и говорил ранее — что шифрование, слава богу, относительно легко выносится в отдельный сервис, не трогая потроха динозавров. Даже если какой-нибудь «широко мыслящий» товарищ понаделал там унутре десятилетних сертификатов и гвоздями приколотил версию openssl, которая умеет максимум какой-нибудь TLS 1.0.

            Лэценкрипт далеко не всегда работает — например если винда.

            Опять безосновательные домыслы. Давно всё работает и на винде (см. напр).

            На потоки сознания и переходы на личности отвечать, если позволите, не буду.

            Не эйджизма ради, а просто для интереса, угадал ли я — вам сколько лет? :)


  1. korsetlr473
    05.12.2021 19:24

    таков вопрос , наверно слышали что все домены в зоне ****.app автоматически идут в ssl сертификатом , как это табртает? где они хранятся ? как и кто их передает когда допустим делегируешь домен на cloudflare


    1. pae174
      05.12.2021 21:55

      / как и кто их передает когда допустим делегируешь домен на cloudflare

      Cloud Flare пользуется Let's Encrypt.

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