27 Август 2016

Использование нескольких SSL сертификатов на одном IP адресе

Using few SSL certificates for one IP address

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

Одним из проявлений этого процесса является массовое внедрение использования шифрования передваемых данных посредством технологии SSL / TLS сертификатов (далее для краткости - просто SSL).

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

Однако, несмотря на очевидную пользу, шифрование данных посредством SSL/TLS имеет и ряд ограничений, вызванных особенностями используемых протоколов.

1. Первым делом соединение

SSL сертификат призван удостоверять принадлежность передаваемых данных для конкретного интернет хоста (или нескольких сразу) по его доменному имени. Стандартом также допускается и выпуск сертификата для IP адресов. Однако, в настоящее время выпуск таких сертификатов остановлен.

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

Ранее провайдеры SSL сертификатов не давали возможности использования различных доменов второго уровня (и их субдоменов) в рамках одного сертификата, вероятно, по причине дополнительной заботе о безопасности систем в случае компрометации такого сертификата. Поэтому в рассматриваемом случае всегда требовалось использование дополнительных IP адресов на сервере, что, помимо некоторых технических моментов, сопряжено также и с вопросами нехватки свободных адресов и, соответственно, как и для всякого ограниченного ресурса, вопросом их стоимости.

Таким образом, для установления первоначального SSL соединения необходимо, чтобы в используемом для этого сертификате фигурировало имя хоста, которое соответствует прописанному в А-записи DNS для него IP адресу. При этом, даже имея несколько сертификатов для различных доменов, для которых корректно сформированы A-записи, нет возможности узнать для какого хоста формируется соединение, и, соответственно, какой сертификат выбирать для его установления.

2. Скажи мне имя своё

Данная проблема привела к включению в протокол SSL специального механизма Server Name Indication (SNI).

Вкратце, он состоит в том, что сначала устанавливается соединение с предоставляемым данным сервером сертификатом. Если же в его составе отсутствует ожидаемое клиентом доменное имя, то серверу предоставляется возможность заменить сертификат на подходящий. Таким образом, появляется возможность использования неограниченного количества доменных имён с различными SSL сертификатами на одном IP адресе.

В настоящее время расширение SNI поддерживается практически всем современным программным обеспечением, включая серверы различных интернет-служб, браузеры и клиенты электронной почты. В частности, используемые для хостинга данного сайта веб-сервер Lighttpd, и почтовые Exim и Dovecot в полной мере поддерживают SNI.

3. Сначала был веб

SSL сертификаты в массе своей используются для защиты передаваемых данных для сайтов в WWW. Поэтому начнём настройку именно с веб-сервера.

Мною используется Lighttpd в операционной системе FreeBSD.

root@beta:~ # uname -v
FreeBSD 11.0-RC2 #0 r304729: Wed Aug 24 06:59:03 UTC 2016     root@releng2.nyi.freebsd.org:/usr/obj/usr/src/sys/GENERIC
root@beta:~ # lighttpd -v
lighttpd/1.4.41 (ssl) - a light and fast webserver
Build-Date: Aug 17 2016 23:13:40

SSL сертификаты для основного имени хоста и домена kostikov.co получены у Let's encrypt. Подробнее о процессе получения и автоматического обновления, а также настройка веб-сервера изложена в статье "Получаем и обновляем SSL сертификат Let's Encrypt".

Здесь и далее предоплагается, что мы испольуем созданную клиентом Let's encrypt структуру хранимых сертификатов. Ещё раз лишь подчеркну, что, поскольку Lighttpd требует единого файла с публичным SSL сертификатом и ключом к нему, следует его создать вручную под именем privandcert.pem и прописать соответствующий симлинк в каталоге live.

root@beta:/usr/local/etc/letsencrypt/archive/kostikov.co # cat privkey1.pem cert1.pem > privandcert1.pem
root@beta:/usr/local/etc/letsencrypt/archive/kostikov.co # ll
total 14
-rw-r--r--  1 root  wheel  1814 27 апр 17:52 cert1.pem
-rw-r--r--  1 root  wheel  1647 27 апр 17:52 chain1.pem
-rw-r--r--  1 root  wheel  3461 27 апр 17:52 fullchain1.pem
-rw-r--r--  1 root  wheel  3518 27 апр 18:10 privandcert1.pem
-rw-r--r--  1 root  wheel  1704 27 апр 17:52 privkey1.pem
root@beta:/usr/local/etc/letsencrypt/archive/kostikov.co # cd ../../live/kostikov.co/
root@beta:/usr/local/etc/letsencrypt/live/kostikov.co # ln -s ../../archive/kostikov.co/privandcert1.pem privandcert.pem
root@beta:/usr/local/etc/letsencrypt/live/kostikov.co # ll
total 0
lrwxr-xr-x  1 root  wheel  35 27 апр 17:52 cert.pem@ -> ../../archive/kostikov.co/cert1.pem
lrwxr-xr-x  1 root  wheel  36 27 апр 17:52 chain.pem@ -> ../../archive/kostikov.co/chain1.pem
lrwxr-xr-x  1 root  wheel  40 27 апр 17:52 fullchain.pem@ -> ../../archive/kostikov.co/fullchain1.pem
lrwxr-xr-x  1 root  wheel  42 27 апр 18:13 privandcert.pem@ -> ../../archive/kostikov.co/privandcert1.pem
lrwxr-xr-x  1 root  wheel  38 27 апр 17:52 privkey.pem@ -> ../../archive/kostikov.co/privkey1.pem

Часть файла конфигурации Lighttpd относящаяся к SSL приобретёт приблизительно следующий вид.

root@beta:~ # cat /usr/local/etc/lighttpd/lighttpd.conf
# --- by Max Kostikov (c) 2010...2016 v.20160610
...
# --- path to Let's Encrypt cert storage
lepath = "/usr/local/etc/letsencrypt/live"

# -- SSL support
$SERVER["socket"] == ":443" {
        ssl.engine = "enable"
        ssl.ca-file = lepath + "/my.server/chain.pem"
        ssl.pemfile = lepath + "/my.server/privandcert.pem"
...
        $HTTP["host"] == "kostikov.co" {
                ssl.ca-file = lepath + "/kostikov.co/chain.pem"
                ssl.pemfile = lepath + "/kostikov.co/privandcert.pem"
        }
...
}
...

Здесь, как видно, в качестве базового сертификата используется содержащий имя хоста для А-записи используемого сервера my.server.

Файл конфигурации Lighttpd также поддерживает использование регулярных выражений, благодаря чему в случае использования большого количества и / или мультидоменных сертификатов его можно существенно сократить.

Перезапустив сервис можно проверить его работоспособность.

root@beta:~ # service lighttpd restart
Performing sanity check on lighttpd configuration:
Syntax OK
Stopping lighttpd.
Waiting for PIDS: 1133.
Starting lighttpd.
root@beta:~ # openssl s_client -tlsextdebug -servername kostikov.co -connect my.server:443
CONNECTED(00000003)
TLS server extension "server name" (id=0), len=0
TLS server extension "renegotiation info" (id=65281), len=1
0001 - <SPACES/NULS>
TLS server extension "EC point formats" (id=11), len=4
0000 - 03 00 01 02                                       ....
TLS server extension "session ticket" (id=35), len=0
TLS server extension "heartbeat" (id=15), len=1
0000 - 01                                                .
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = www.kostikov.co
verify return:1
---
Certificate chain
 0 s:/CN=www.kostikov.co
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=www.kostikov.co
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-384, 384 bits
---
SSL handshake has read 3187 bytes and written 485 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: ...
    Session-ID-ctx:
    Master-Key: ...
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    ...

    Start Time: 1472315403
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
QUIT
DONE

Как видно, всё корректно - используется необходимый сертификат, а ошибки отстутствуют.

4. Защита почтовых каналов

Для обслуживающих почту серверов SMTP Exim и POP/IMAP Dovecot 2 настройка механизма использования нескольких SSL сертификатов для одного IP адреса выглядит следующим образом.

root@beta:~ # exim --version
Exim version 4.87 #0 (FreeBSD 11.0) built 17-Aug-2016 18:01:01
...
root@beta:~ # dovecot --version
2.2.25 (7be1766)

Конфигурация Dovecot, в отличие от Lighttpd, не имеет возможности использовать гибкий механизм регулярных вражений в секции описания сертификатов local_name для отличных от основного имён хостов, которые, напомню, должны быть прописаны в качестве серверов в почтовом клиенте POP/IMAP. Поэтому для каждого из них потребуется создать свою, аналогичную приведённой, секцию.

root@beta:~ # cat /usr/local/etc/dovecot/dovecot.conf
# --- by Max Kostikov (c) 2010...2016 v.20160505
...
ssl = yes
...
ssl_ca = </usr/local/etc/letsencrypt/live/my.server/chain.pem
ssl_cert = </usr/local/etc/letsencrypt/live/my.server/cert.pem
ssl_key = </usr/local/etc/letsencrypt/live/my.server/privkey.pem
local_name kostikov.co {
  ssl_ca = </usr/local/etc/letsencrypt/live/kostikov.co/chain.pem
  ssl_cert = </usr/local/etc/letsencrypt/live/kostikov.co/cert.pem
  ssl_key = </usr/local/etc/letsencrypt/live/kostikov.co/privkey.pem
}
...

Exim, в отличие от предыдущего примера, предоставляет чрезвычайно мощный и гибкий синтаксис в своём конфигурационном файле, поэтому для массового использования набора различных SSL сертификатов в рамках одного IP адреса можно ограничиться весьма краткой записью.

root@beta:~ # cat /usr/local/etc/exim/configure
# $Cambridge: exim/exim-src/src/configure.default,v 1.14 2009/10/16 07:46:13 tom Exp $
# --- by Max Kostikov (c) 2010...2016 v.20160710
...
# -- Path to Let's encrypt cert storage
LEPATH = /usr/local/etc/letsencrypt/live
...
tls_certificate = ${if exists{LEPATH/${tls_sni}/fullchain.pem}{LEPATH/${tls_sni}/fullchain.pem}{LEPATH/my.server/fullchain.pem}}
tls_privatekey  = ${if exists{LEPATH/${tls_sni}/privkey.pem}{LEPATH/${tls_sni}/privkey.pem}{LEPATH/my.server/privkey.pem}}
...

В данном случае в качестве пути к сертификату используется специальная переменная SSL соединения ${tls_sni} которая содержит имя хоста, к которому обращается клиент. При этом, если для содержащегося в ней хоста по сформированному пути имеется сертификат, то используется он, в противном случае, а также при начально соединении, используется сертификат основного имени сервера my.server.

При данной записи выражения для выбора сертификата в случае, если в одном и том же сертификате используются различные домены, с тем, чтобы происходила корректная подстановка, в соотстветствующем каталоге хранилища Let's Encrypt можно добавить простые симлинки на существующие пути. К примеру, если помимо kostikov.co небоходимо ещё и обеспечение передачи почты для содержащегося в SSL сертификате mail.kostikov.co, то достаточно будет выполнить одну команду.

root@beta:~ # cd /usr/local/etc/letsencrypt/live
root@beta:/usr/local/etc/letsencrypt/live # ln -s kostikov.co mail.kostikov.co
root@beta:/usr/local/etc/letsencrypt/live # ll
total 10
...
drwxr-xr-x  2 root  wheel  512  3 июля  03:00 kostikov.co/
lrwxr-xr-x  1 root  wheel   11 27 авг.  19:00 mail.kostikov.co@ -> kostikov.co
drwxr-xr-x  2 root  wheel  512 26 июня  03:00 my.server/
...

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

Замечание. Для SMTP сессий имя сервера, которое используется в MX-записи DNS, должно содержаться и в используемом сертификате.

Перезапустив демоны Dovecot и Exim аналогичным использованным для веб-сервера способом, не забыв, конечно же, использовать соответствующие почтовым сервисам порты (см. ваши конфигурации), можно удостоверится в работоспособности и корректном выборе сертификата для ваших доменов на сервере.

5. PROFIT!

Статья была полезной? Тогда прошу не стесняться и поддерживать деньгами через PayPal или Яндекс.Деньги.