Использование SSL-шифрование на сайтах приобретает уже почти обязательный характер: Google с этого года начал агрессивно предупреждать о небезопасном соединении с сайтами, ряд платежных шлюзов требуют безопасное подключение на сайтах (например, Яндекс.Касса). Установка SSL-сертификата на сайт требует достаточно сложной технической настройки веб-сервера (nginx, например). Одним из аспектов этой настройки является использование «сшивания» (stapling) SSL-сертификатов вплоть до корневого для ускорения установления безопасного соединения из браузеров к сайтам.

SSL stapling позволяет сэкономить для нового посетителя сайта 0,1-1 секунду (за счет экономии 1-2 запросов за промежуточными сертификатами с учетом DNS-запросов, установления соединения и получения данных, каждый из запросов может выполняться до 500 мс в случае 95 перцентиля пользователей). По умолчанию, SSL stapling выполняется для всех сертификатов, загруженных в Айри.

Обычно цепочка SSL-сертификатов, которую должен запросить браузер, выглядит следующим образом:
SSL-сертификат сайта — Промежуточный SSL-сертификат [- Промежуточный SSL-сертификат 2] — Корневой SSL-сертификат

Корневые SSL-сертификаты загружены в браузер (в целях избежания их подделки при передачи по сети). Промежуточные SSL-сертификаты могут быть получены из SSL-сертификата сайта («родительский» сертификат, которым подписан данный сертификат, внесен в соответствующем поле данного сертификата). Как это корректно сделать, чтобы сшить все сертификаты и ускорить загрузку сайта в браузере?

Openssl


Основным рабочим инструментом будет библиотека openssl: она позволяет реализовать преобразование сертификатов и извлечение необходимой информации в текстовом формате (в сертификате информация закодирована либо в бинарном формате (DER), либо в base64 (PEM)).

Если на сервере вы подключаете SSL-шифрование для сайта, то сама библиотека, скорее всего, у вас уже установлена. Проверить, например, для какого домена (Canonical name) выпущен сертификат (если он в PEM формате, обычно в таком формате сертификаты выпускаются и передаются для установки на сервер) можно так:
openssl x509 -in ФАЙЛ_СЕРТИФИКАТА -subject -noout

Здесь x509 — входящий формат сертификата, in — ключ для входного файла, subject — указание вывести CN сертификата, а noout — запрет на вывод самого сертификата (в PEM формате).

Получаем «родительский» сертификат


Алгоритм «сшивки» сертификатов достаточно простой: нам нужно из сертификата извлечь путь к «родительскому» сертификату (тому сертификату, которым подписанный данный) и получить этот «родительский» сертификат по извлеченному пути. Простого набора ключей для openssl найти не удалось, поэтому сделаем это с помощью вывода всей информации о сертификате в тестовом формате с помощью ключа text:
openssl x509 -in "ФАЙЛ_СЕРТИФИКАТА" -text -noout

После этого нам нужно лишь выделить поле Issuers и получить из него URL. Это можно сделать, например, так:
openssl x509 -in "ФАЙЛ_СЕРТИФИКАТА" -text -noout | grep Issuers | awk '{sub(/.*http/,"http");print $0}'

Получив URL сертификата, скачиваем его любым удобным образом. Например, через curl:
curl --user-agent "Mozilla/5.0 (compatible; Airee-Speedup/1.0; +http://airee.ru/robots)" $issuer -o /tmp/stapling.crt 2>/dev/null

Запускаем рекурсию


Описанную выше процедуру можно выполнять в цикле, пока из сертификата извлекается путь к «родительскому». Но есть один нюанс. Подписывающие сертификаты удостоверяющих центров обычно хранятся в бинарном формате, поэтому нам нужно будет проверить формат сертификата и преобразовать его к текстовому (чтобы корректно включить в финальную цепочку сертификатов, которая будет записана в PEM формате).

Для проверки формата сертификата воспользуемся verify от openssl:
openssl verify /tmp/stapling.crt 2>&1 | grep "unable to load")

В случае возникновения ошибки чтения загруженного сертификата нам нужно будет его преобразовать из бинарного формата в base64:
openssl x509 -inform der -in /tmp/stapling.crt -out /tmp/stapling.crt

После преобразования сертификата мы можем спокойной скопировать его в конец нашей цепочки, определить путь к «родительскому» сертификату и продолжить составление цепочки (если требуется).

Правильный порядок сертификатов в цепочке


В финале должен получиться файл с цепочкой сертификатов следующего содержания:
-----BEGIN CERTIFICATE-----
сертификат сайта в формате base64
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
промежуточный сертификат в формате base64
-----END CERTIFICATE-----
[-----BEGIN CERTIFICATE-----
возможно, еще один промежуточный сертификат
-----END CERTIFICATE-----]
-----BEGIN CERTIFICATE-----
корневой сертификат в формате base64
-----END CERTIFICATE-----

Браузерам сам корневой сертификат в цепочке не требуется: он у них уже есть. Но он требуется для верификации цепочки самому nginx с включенной настройкой ssl_stapling.

Итог


Для составления цепочки сертификатов из данного требуются следующие действия:

1. Скопировать данный сертификат в начало цепочки (cat).
2. Получить URL «родительского» сертификата (openssl + awk / sed).
3. Загрузить «родительский» сертификат (curl / wget).
4. Переформатировать «родительский» сертификат (openssl).
5. Скопировать «родительский» сертификат в цепочку (cat).
6. Повторить шаги 2-5 или завершить.
Поделиться с друзьями
-->

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


  1. kolu4iy
    09.01.2017 16:26

    … но если вдруг вы решили не тратиться на EV-сертификат, то старый добрый дядюшка startssl кроме выдачи бесплатного сертификата, заботливо выгрузит вам всю цепочку в архиве, причем в видах пригодных для различных веб-серверов. Вам остаётся только подставить эту цепочку.


    1. Erelecano
      09.01.2017 16:42
      +3

      Это тот startssl который выкинут из доверенных за мошенничество?
      Спасибо, нет.


    1. xvitaly
      09.01.2017 16:47

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


      1. kolu4iy
        09.01.2017 17:56

        Эм… Не проблема. Letsencrypt генерирует fullchain.pem, который точно также не требует сшивания…


  1. welcomerooot
    09.01.2017 16:44
    +1

    Установка SSL-сертификата на сайт требует достаточно сложной технической настройки веб-сервера (nginx, например).

    Ну, такое.


  1. Erelecano
    09.01.2017 17:41

    > Установка SSL-сертификата на сайт требует достаточно сложной технической настройки веб-сервера (nginx, например).

    Для настройки на nginx'е нового домена с честным ssl-сертификатом от LE и с A+ рейтингом по ssllabs.com у меня уходит в районе 5 минут(из которых минуту оно сертификат получает). Кажется у вас что-то идет не так, если это — сложная техническая настройка.


    1. sunnybear
      09.01.2017 19:07
      +2

      Ту конфигурацию, которую выкладываете за 5 минут, скорее всего, вы собирали несколько часов или даже дней в первый раз. Речь об этом.
      Но вы можете выложить вашу конфигурацию nginx относительно ssl — тогда будет понятно, есть ли в ней недоработки или ошибки. Особенно, в части поддерживаемых шифров и обратной совместимости.


      1. Erelecano
        09.01.2017 19:14

        Понимаете какая штука. Мне нет нужды выкладывать что-то и доказывать кому-то.
        https://www.ssllabs.com/ssltest/analyze.html?d=lindon.pw&latest
        A+
        https://www.ssllabs.com/ssltest/analyze.html?d=ip.lindon.pw&latest
        A+
        Да, при этом у меня целенаправлено отключены TLSv1 и TLSv1.1, то есть то, что вы видите, что старые ОС туда не могут зайти это не ошибка конфигурирования, на личных проектах я использую такой вариант, потому что некрофилам ко мне ходить незачем.

        Ваши же пляски с cat'ом не тянут на статью. А уж гордая подпись «Технический директор» намекает нам неиллюзорно, что технический директор вы разве что у своего локалхоста и фирмы «Рога и копыта».

        Не нужны пляски с кэтом, а тем кому нужны не нужны статьи уровня «Я у мамы какир».
        Либо ЦА хабра изменилась настолько, что здесь теперь только полные идиоты, либо вы промахнулись ресурсом для размещения свой бесполезной статьи.


      1. Suvitruf
        09.01.2017 19:17
        -1

        А там что-то сложное?
        Я всегда такое использую:

        ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers "RC4:HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
        ssl_prefer_server_ciphers on;
        


        И, судя по geotrust ssl checker, там всё вполне неплохо.

        Или я что-то упускаю?


        1. sunnybear
          09.01.2017 19:19
          +1

          SSLv3 признан небезопасным почти год назад
          https://access.redhat.com/articles/1232123


          1. Suvitruf
            09.01.2017 19:25

            Правда

            Exploiting this vulnerability is not easily accomplished
            Я в этих штуках не особо разбираюсь, но мы эти сервера используем только для API сервисов, никакого доступа из браузера. Стоит ли нам беспокоиться по поводу этой уязвимости?


            1. sunnybear
              09.01.2017 19:28
              +1

              Потенциальная дыра. Т.е. те, кто захотят вас взломать, будут иметь на руках преимущество. Беспокоиться или нет — вам решать.

              В настройке SSL очень много нюансов. Есть множество статей, как правильно настроить SSL для nginx, но каждая из них упускает какой-то момент из виду: где-то не хватает сшивки, где-то шифров, где-то эллиптического шифрования. И т.д.


        1. varnav
          10.01.2017 12:00

          А судя по SSL labs test?


          1. Suvitruf
            10.01.2017 12:06

            Судя по нему, всё плохо :C


      1. mxms
        09.01.2017 22:58

        Действительно, автор преувеличил. Получение SSL сертификата и его настройка на веб-сервере занимает никак не 5 минут, а всего 2. Это потому, что помимо Let's Encrypt есть ещё и Mozilla SSL Configuration Generator.


        1. varnav
          10.01.2017 12:41
          +1

          Он не идеален. Например, он зачем то выдаёт параметр длины DH в конфигурациях modern, где DH не используется по определению.


          1. mxms
            10.01.2017 15:28

            Well, nobody's perfect


        1. Erelecano
          10.01.2017 19:41

          5 минут это с момента, как я решаю «А мне нужен еще один домен который будет что-то отдавать» и до момента, как я запускаю ssllabs на тестирование обычно(я засекал время). То есть это зайти на сервер, скопировать шаблонный конфиг nginx'а для домена, исправить пути, сделать nginx -t, потом сделать ему reload, получить сертификат и включить ssl для домена.

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


    1. varnav
      10.01.2017 11:59
      +2

      Чтобы уходило 5 минут на настройку, надо несколько недель а то и месяцев потратить на изучение многочисленных тонкостей TLS/SSL/HTTPS и особенности правильных настроек под него.


  1. ValdikSS
    09.01.2017 23:29
    +2

    Вы путаете понятия Stapling и Chaining. То, что вы описываете — Chaining, а Stapling (и директива ssl_stapling nginx) — передача клиенту подписанного OSCP-ответа с сервера владельца корневого сертификата, чтобы клиенту не пришлось самостоятельно к нему обращаться.


    1. sunnybear
      10.01.2017 00:10

      вполне возможно. Но Stapling невозможен без chaining?


      1. ValdikSS
        10.01.2017 00:14

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


        1. sunnybear
          10.01.2017 00:17

          У нас примерно 10% сертификатов приходят одиночными. При наших объемах это актуальная проблема автоматизации: чтобы не из архива доставать или вручную определять, а через сертификат актуальную цепочку получать. Возможно, есть и другие пути автоматизации.


        1. simpleadmin
          10.01.2017 10:10
          +1

          Проблема, скорее, надумана, нежели реальна.

          На хостинг- cdn- сервисах одиночных (или с цепочкой от другого удостоверяющего центра) приходит порядка половины. К сожалению знаю не по наслышке.
          $cat issuers.sh
          #!/usr/local/bin/bash
          
          cmd_grep='/usr/bin/grep '
          cmd_openssl='/usr/bin/openssl '
          cmd_cut='/usr/bin/cut '
          cmd_fetch='/usr/bin/fetch '
          
          
          tmp_der='tmp.der'
          tmp_cert='tmp.cert'
          
          #------------------------------------------------------------------------------
          usage () {
              #printf "function ${FUNCNAME}\n"
              printf "Error!\nUsage:\t\"$0 certificate.pem\"\n"
              exit 1
          }
          #------------------------------------------------------------------------------
          if [ "X$1" = "X" ]
          then
              usage
          else
              cp $1 $tmp_cert
              chain_cert="chain.pem"
          fi
          
          i=0
          while :
          do
              issuer=`$cmd_openssl x509 -in $tmp_cert -noout -text | $cmd_grep 'CA Issuers' | $cmd_cut -d : -f 2,3`
              if [ "X$issuer" != "X" ]
              then
                  echo $i
                  echo $issuer
                  tmp_pem=$1$i.pem
                  $cmd_fetch $issuer --output=$tmp_der
                  is_pem=`$cmd_grep -c CERTIFICATE $tmp_der`
                  printf "IS PEM:\t[$is_pem]\n"
                  #echo "$tmp_der -> $tmp_pem"
                  if [ $is_pem -ne 0 ]
                  then
                      echo "PEM($tmp_der) -> PEM($tmp_pem)"
                      cp -f $tmp_der $tmp_pem
                  else
                      echo "DER($tmp_der) -> PEM($tmp_pem)"
                      echo "$cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem"
                      $cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem
                  fi
                  cp $tmp_pem $tmp_cert
                  let "i+=1"
                  #sleep 2
              else
                  break
              fi
          done
          
          if [ $i -gt 0 ]
          then
              echo "cat ./$1* > $chain_cert"
              cat ./$1* > $chain_cert
              printf "Certificate chain:\n"
              ls -l $chain_cert
              #ls | grep -Ev ^ya.pem$ | xargs rm
          fi
          


          1. motienko
            10.01.2017 11:08

            кстати, 'CA Issuers' может быть не только в x509 der или pem формате, а еще pkcs7, например, у того же letsencrypt

            Получить в PEM так:

            wget http://apps.identrust.com/roots/dstrootcax3.p7c
            
            openssl pkcs7 -inform der -in dstrootcax3.p7c -print_certs -out dstrootcax3.pem
            
            


            1. motienko
              10.01.2017 11:14
              +1

              тогда надо в скрипт добавить несколько строк

                     else
                          echo "DER($tmp_der) -> PEM($tmp_pem)"
                          echo "$cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem"
                          $cmd_openssl x509 -inform der -in $tmp_der -out $tmp_pem
                          if [ $? -ne 0 ]
                          then
                              echo "May be PKCS7"
                              echo "PKCS7 DER($tmp_der) -> PEM($tmp_pem)"
                              echo "$cmd_openssl pkcs7 -inform der -in $tmp_der -print_certs -out $tmp_pem"
                              $cmd_openssl pkcs7 -inform der -in $tmp_der -print_certs -out $tmp_pem
                          fi
                      fi
                      cp $tmp_pem $tmp_cert
                      let "i+=1"
              
              


            1. simpleadmin
              10.01.2017 11:36

              так PKS7 и есть DER-контейнер содержащий один или несколько сертификатов (без ключа в отличии от PKS12), и, конечно, в этих сертификатах может присутствовать расширение Authority Information Access и как следствие блок CA Issuers


  1. chaturanga
    10.01.2017 09:36
    +3

    Чейнинг с автоматизированным скриптом на bash — https://habrahabr.ru/post/304458/


  1. varnav
    10.01.2017 11:56

    У меня stapling работает и без корневого, зато если корневой присутствует, то тест от SSL labs ругается на contains anchor.