О STARTTLS

2016-09-18

SSL, который Secure Sockets Layer, бывает разный. И как минимум с 1999 года он называется TLS — Transport Layer Security. Впрочем, сути это не меняет.

TLS logo

Внезапно оказалось, что почти все протоколы Интернета передают ценные данные открытым текстом. И любой чувак, который сможет перехватить трафик, сможет извлечь эти ценные данные. Конечно, фоточки котиков и фильмы известного содержания — не очень-то и ценны. Но вот логин-пароль для платного доступа к котикам, или идентификатор сессии, когда логин-пароль уже введён, — уже ценны.

Поэтому придумали SSL. С точки зрения приложения — почти такой же обычный TCP сокет, вот только передаваемые данные зашифрованы. И никакой чувак посередине ваших котиков больше не увидит. Если, конечно, он не работает в правильных организациях, чьей обязанностью является бдить.

SSL/TLS — это гибридная криптосистема. У нас есть клиент, и есть сервер. Клиент всегда подключается к серверу. У сервера есть приватный ключ, который всегда при нём. И у сервера есть публичный ключ, в виде сертификата, подписанного каким-то центром сертификации. Ну или подписанный ключом самого сервера — самоподписанный сертификат. Сертификат содержит имя сервера, сведения о его владельце и прочее. Правильность этих сведений подтверждается подписью. Центры сертификации подписывают сертификаты за деньги. Это организации, которые дорожат своей репутацией, а мы им доверяем. И на этом доверии всё и держится.

Клиент, когда подключается к серверу, получает его сертификат. Он проверяет: а действительно ли это сертификат того сервера, к которому мы подключаемся? А не истёк ли срок действия сертификата? А доверяем ли мы тому центру сертификации, что подписал этот сертификат? Иногда сертификат бывает и у клиента, и он предъявляет его серверу, а сервер производит подобные же проверки. Если всё ок, то идём дальше.

Используя ассиметричное шифрование, приватные ключи и публичные ключи из сертификатов, клиент и сервер договариваются о ключе сессии. Это ключ уже симметричного шифрования. И этим ключом будут зашифрованы последующие данные. Всё секурно и красиво, если вы пользуетесь свеженькими реализациями TLS. Все SSL, версии от 1.0 до 3.0, нынче считаются небезопасными. Безопасные — это TLS 1.1 или 1.2, и, может быть, TLS 1.0.

Гибридная криптосистема

Хорошо, у нас есть надёжный способ шифровать TCP соединения. Но у нас есть и старые добрые, уже работающие протоколы. Как добавить к ним шифрование?

Поначалу решили: а давайте назовём это новым протоколом и повесим на отдельный TCP порт. К имени протокола как правило добавляют буковку S — Secure. Так HTTP стал HTTPS и переехал с порта 80 на порт 443. FTP стал FTPS, вместо портов 21-20 стал использовать порты 990-989. Не путать с SFTP, который использует шифрование SSH, а не SSL. SMTP, протокол пересылки почты, стал SMTPS и перехал с порта 25 на порт 465. В почте вообще много протоколов: POP3→POP3S — 110→995, IMAP→IMAPS — 143→993. Даже в Jabber, он же XMPP, сначала пошли по этому пути, для клиентских SSL подключений вместо порта 5222 взяли порт 5223, для междусерверных подключений вместо 5269 взяли порт 5270. О боже, даже telnets придумали, на порту 992.

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

SSL Handshake

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

Хороший пример: виртуальный хостинг в HTTP, он перестал работать в HTTPS. У нас есть один HTTP сервер, а обслуживает он несколько сайтов, с разными доменными именами. В HTTP/1.1 клиент в каждом запросе указывает заголовок Host, куда помещает соответствующую часть URL. Сервер смотрит на этот заголовок, и выбирает, к какому обслуживаемому сайту относится запрос. Всё просто. Но в случае с SSL клиент запрашивает и проверяет сертификат сервера ещё до передачи каких-либо заголовков. И в сертификате содержится доменное имя сервера. И клиент удостоверяется что это именно то имя, к которому он обращается. По-хорошему, у каждого сайта должен быть свой сертификат. А здесь получается, что на одном 443 порту может быть только один сертификат, который не может соответствовать всем сайтам сразу. Вот и не работает.

В попытке обойти эти трудности решили пойти другим путём. Расширить протоколы, чтобы можно было начать сеанс связи без шифрования, а потом переключиться на шифрование, если и клиент, и сервер его поддерживают. Это красиво называется Opportunistic TLS. А команда, включающая шифрование, называется STARTTLS.

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

В случае SMTP это выглядит примерно так. Можно проверить обычным telnet.

% telnet smtp.gmail.com 25 
Trying 2a00:1450:4010:c02::6c...
Connected to gmail-smtp-msa.l.google.com.
Escape character is '^]'.
220 smtp.gmail.com ESMTP d130sm3364531lfd.12 - gsmtp
EHLO localhost
250-smtp.gmail.com at your service, [2a02:2698:5425:c799:58de:766c:9bc4:25f5]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
STARTTLS
220 2.0.0 Ready to start TLS

Клиент спрашивает у сервера, какие фичи он поддерживает: командой EHLO. Сервер говорит, что, среди прочего, он умеет STARTTLS: 250-STARTTLS. Клиент говорит STARTTLS. Сервер говорит: я готов, начинай. После этого и надо начинать TLS, только telnet этого не умеет.

STARTTLS

В XMPP, так как это протокол на основе XML, STARTTLS выглядит как XML тэг: <tls:starttls/>.

В OpenSSL есть встроенный клиент, который умеет делать STARTTLS. Например для SMTP.

% openssl s_client -starttls smtp -crlf -connect smtp.gmail.com:25 
CONNECTED(00000003)
...
---
Server certificate
...
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority G2
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3980 bytes and written 466 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: CE85D4D6203D288C41D56E7A6990250BBC0DADED2651335E2B0881032AA1200C
    Session-ID-ctx: 
    Master-Key: 50B893A1152F2862115396C2EB7FF43576C8D7B36D1B62F1F9E8C5ABC5293EC4DA3222674C639C146B3AA928D48537D0
    ...        
    Start Time: 1474131521
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
250 SMTPUTF8

Клиент довольно убогий, но оно и понятно, ибо только для тестов. Он очень забавно воспринимает некоторые символы, поэтому команды SMTP лучше набирать маленькими буквами: rcpt to:, потому что большая R заставляет этот клиент делать реконнект. Тем не менее, он умеет делать STARTTLS для smtp, pop3, imap и ftp (параметр -starttls).

Использовать один и тот же порт для незашифрованных и для шифрованных соединений оказалось так удобно, что отдельные порты для SSL быстренько объявили устаревшими. Так что теперь стандартный и кошерный способ делать TLS — это STARTTLS.

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

% telnet smtp.gmail.com 25                                         
Trying 2a00:1450:4010:c08::6c...
Connected to gmail-smtp-msa.l.google.com.
Escape character is '^]'.
220 smtp.gmail.com ESMTP w15sm2845884lff.1 - gsmtp
EHLO localhost
250-smtp.gmail.com at your service, [2a02:2698:5425:c799:58de:766c:9bc4:25f5]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8
MAIL FROM: <[email protected]>
530 5.7.0 Must issue a STARTTLS command first. w15sm2845884lff.1 - gsmtp

А вот с HTTPS как-то не сложилось. Возможно, потому, что в HTTP не предусмотрено длительных сессий между клиентом и сервером, STARTTLS просто некуда воткнуть. Впрочем, подобная попытка предпринималась в RFC 2817, но не прижилась. Там предлагалось делать Upgrade протокола до TLS, примерно как сейчас делается для WebSocket или для одновременной поддержки HTTP и HTTP/2.

OpenSSLный клиент можно и для SSL wrapper использовать. (Заголовок Host для запроса в HTTP/1.1 является обязательным)

% openssl s_client -crlf -connect www.example.com:443
CONNECTED(00000003)
...
---
Server certificate
...
subject=/C=US/ST=California/L=Los Angeles/O=Internet Corporation for Assigned Names and Numbers/OU=Technology/CN=www.example.org
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance Server CA
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3393 bytes and written 431 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: E55C3E29A1EC4A52D9BB0C18409A9AF84717DD73EED8F1F104C6751D3610DCAB
    Session-ID-ctx: 
    Master-Key: E8951CABC1DA38388F971003C668370DDA72C5D471FF87A670A7BE51D61D97851487D56B944FBBDEDE5B791632875B52
    ...
    Start Time: 1474133342
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---
GET / HTTP/1.1
Host: www.example.com

HTTP/1.1 200 OK
...
Content-Type: text/html
Content-Length: 1270

<!doctype html>
<html>
...

Но сейчас виртуальные хостинги вполне живут под HTTPS, выкручиваются. Можно взять wildcard сертификат, на домен *.myhost.example, и все поддомены смогут прикрываться этим сертификатом. Можно включить в один сертификат несколько доменных имён, чаще всего с помощью расширения для сертификатов под названием subjectAltName. Мы просто указываем в одном сертификате доменные имена всех сайтов на нашем сервере. Вот только для добавления ещё одного сайта сертификат придётся перевыпускать.

Ну а самый кошерный способ: Server Name Indication, описанный аж в 2003 году в RFC 3546, но реально реализованный лишь в последние годы. Это расширение к самому протоколу TLS, имя сервера передаётся открытым текстом ещё до обмена сертификатами, и сервер может выбрать, какой сертификат возвращать. Полноценный аналог заголовка Host. И полноценная поддержка виртуального хостинга с разными сертификатами для разных сайтов.

SNI example

В HTTP/2 всё остаётся без существенных изменений. Спецификация не требует обязательного TLS, существуют URL со схемой http и https. Но существующие реализации работают только с TLS. При этом требуется TLS 1.2 или выше и поддержка Server Name Indication.

Тем не менее, для HTTP/2 есть забавный черновик Opportunistic Security for HTTP. Чтобы для http URL работал TLS, если клиент с сервером договорятся. Вместо одной команды STARTTLS в этом черновике предполагается обмен JSONами.