Глава 1. OpenSSL

OpenSSL — проект с открытым исходным кодом, состоящий из криптографической библиотеки и набора инструментов SSL/TLS. Цитата с веб-сайта проекта:

Проект OpenSSL Project — это совместный труд по разработке надёжного полнофункционального Open Source-инструментария коммерческого класса, реализующего протоколы Secure Sockets Layer (SSL) и Transport Layer Security (TLS), а также полнофункциональную криптографическую библиотеку общего назначения. Проект управляется сообществом добровольцев со всего мира, использующим Интернет для взаимодействия, планирования и разработки инструментария OpenSSL, а также связанной с ним документации.

OpenSSL является стандартом де-факто в этой области и имеет продолжительную историю. Первоначально проект стартовал в 1995 году под названием SSLeay1, тогда его разработкой занимались Eric A. Young и Tim J. Hudson. Рождение проекта OpenSSL произошло в конце 1998 года, когда Эрик и Тим прекратили свою работу над SSLeay, чтобы работать над коммерческим инструментарием SSL/TLS под названием BSAFE SSL-C в RSA Australia.

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

У OpenSSL две лицензии: OpenSSL и SSLeay. Обе схожи с лицензией BSD с оговорками о рекламе. Эти лицензии долгое время были источниками раздора, поскольку ни одна из них не считается совместимой с семейством лицензий GPL. Именно по этой причине программы, лицензированные по лицензии GPL, часто отдают предпочтение GnuTLS.

Начало работы

Если вы используете одну из Unix-платформ, начать работать с OpenSSL легко; практически гарантированно, что в вашей системе он уже есть. Единственная проблема, с которой вы можете столкнуться — то, что у вас может не оказаться последней версии. В данном разделе подразумевается, что вы используете Unix-платформу, потому что это естественная среда для OpenSSL.

Пользователи Windows, как правило, загружают двоичные файлы, что может немного усложнить ситуацию. В простейшем случае, если вам нужны только утилиты командной строки, на основном сайте OpenSSL для Windows-бинарников указана ссылка на сборку от Shining Light Productions3. Во всех остальных ситуациях вам необходимо убедиться, что вы не пытаетесь совместно использовать бинарники, скомпилированные на основе разных версий OpenSSL, иначе могут возникать сбои, которые трудно будет выявить и устранить. Наилучшим подходом будет использование единого пакета программ, который включает в себя всё, что вам нужно. Например, если вы хотите запускать Apache на Windows, вы можете получить требуемые бинарники в сборке Apache Lounge4.

Определение версии и конфигурации OpenSSL

Перед тем, как что-либо сделать, вам необходимо узнать, какую версию OpenSSL вы используете. Например, вот так я получаю информацию о версии с помощью команды openssl version на Ubuntu 12.04 LTS — системе, которую я буду использовать в примерах этой главы:

$ openssl version
OpenSSL 1.0.1 14 Mar 2012

На момент написания этого материала происходил переход от OpenSSL 0.9.x к OpenSSL 1.0.x. Версия 1.0.1 особенно важна, поскольку это первая версия, поддерживающая TLS 1.1 и 1.2. Поддержка более новых протоколов является частью глобального тренда, поэтому вполне вероятно, что в ближайший период мы нередко будем сталкиваться с проблемами совместимости.

Примечание: Часто сборщики различных операционных систем модифицируют исходный код OpenSSL, обычно для устранения выявленных проблем. Однако название проекта и номер версии, как правило, оставляют неизменными, и нет никаких признаков того, что данный код на самом деле является ответвлением исходного проекта и будет вести себя по-другому. Например, версия OpenSSL, используемая в Ubuntu 12.04 LTS5, основана на OpenSSL 1.0.1c. На момент написания этого материала полное имя установочного пакета было openssl 1.0.1-4ubuntu5.16, и этот пакет включал исправления для многих проблем, которые были выявлены с течением времени.

Для получения полной информации о версии используйте опцию -a:

$ openssl version -a
OpenSSL 1.0.1 14 Mar 2012
built on: Fri Jun 20 18:54:15 UTC 2014
platform: debian-amd64
options:  bn(64,64) rc4(8x,int) des(idx,cisc,16,int) blowfish(idx)
compiler: cc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN ↩
-DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -g -O2 -fstack-protector --param=ssp-buffer-size↩
=4 -Wformat -Wformat-security -Werror=format-security -D_FORTIFY_SOURCE=2 ↩
-Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DOPENSSL_NO_TLS1_2↩
_CLIENT -DOPENSSL_MAX_TLS1_2_CIPHER_LENGTH=50 -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 ↩
-DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256↩
_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH↩
_ASM
OPENSSLDIR: "/usr/lib/ssl"

Последняя строка в данном выводе (/usr/lib/ssl) особенно интересна, поскольку она говорит вам о том, где OpenSSL будет искать собственную конфигурацию и сертификаты. В моей системе это местоположение, по существу, является псевдонимом для директории /etc/ssl, где Ubuntu хранит файлы, связанные с TLS:

lrwxrwxrwx  1 root root   14 Apr 19 09:28 certs -> /etc/ssl/certs
drwxr-xr-x  2 root root 4096 May 28 06:04 misc
lrwxrwxrwx  1 root root   20 May 22 17:07 openssl.cnf -> /etc/ssl/openssl.cnf
lrwxrwxrwx  1 root root   16 Apr 19 09:28 private -> /etc/ssl/private

Директория misc/ содержит несколько дополнительных скриптов, наиболее интересными из которых являются скрипты, позволяющие реализовать приватный центр сертификации или удостоверяющий центр (УЦ).

Сборка OpenSSL

В большинстве случаев вы будете использовать версию OpenSSL, поставляемую с операционной системой, но иногда есть веские причины для обновления. Например, ваша система могла застрять на OpenSSL 0.9.x, не поддерживающей новые версии протокола TLS, но даже если в вашей системе правильная версия OpenSSL, в ней может не быть необходимого вам функционала. Например, на Ubuntu 12.04 LTS нет поддержки SSL 2 в команде s_client. Хотя отсутствие по умолчанию поддержки этой версии SSL является правильным решением, вам может понадобиться данный функционал, если вы регулярно тестируете другие серверы на поддержку SSL 2.

Вы можете начать с загрузки самой последней версии OpenSSL (в моем случае, 1.0.1p):

$ wget http://www.openssl.org/source/openssl-1.0.1p.tar.gz

Следующий шаг — конфигурирование OpenSSL перед компиляцией. В большинстве случаев системная версия оставляется в покое и OpenSSL устанавливается в другое место. Например:

$ ./config \
--prefix=/opt/openssl \
--openssldir=/opt/openssl \
enable-ec_nistp_64_gcc_128

Параметр enable-ec_nistp_64_gcc_128 активирует оптимизированные версии некоторых часто используемых эллиптических кривых. Данная оптимизация зависит от функции компилятора, которая не может быть обнаружена автоматически, поэтому по умолчанию этот параметр отключен.

Примечание: Если вы компилируете версию OpenSSL из ветки 1.1.x или выше, то, по крайней мере в некоторых системах, вам нужно будет использовать параметр no-shared для создания статически скомпилированных инструментов командной строки. Вы поймёте, что нужно использовать этот параметр, если (после компиляции) при попытке вызвать бинарник OpenSSL он будет жаловаться на то, что не может найти некоторые требуемые ему разделяемые библиотеки.

Затем нужно будет выполнить следующие команды:

$ make depend
$ make
$ sudo make install

В итоге в директории /opt/openssl у вас будет такое содержимое:

drwxr-xr-x 2 root root  4096 Jun  3 08:49 bin
drwxr-xr-x 2 root root  4096 Jun  3 08:49 certs
drwxr-xr-x 3 root root  4096 Jun  3 08:49 include
drwxr-xr-x 4 root root  4096 Jun  3 08:49 lib
drwxr-xr-x 6 root root  4096 Jun  3 08:48 man
drwxr-xr-x 2 root root  4096 Jun  3 08:49 misc
-rw-r--r-- 1 root root 10835 Jun  3 08:49 openssl.cnf
drwxr-xr-x 2 root root  4096 Jun  3 08:49 private

Директория private/ пуста, но это нормально: у вас ещё нет каких-либо закрытых ключей. С другой стороны, вы, вероятно, удивитесь, узнав, что директория certs/ тоже пуста. В OpenSSL не включено никаких корневых сертификатов; подразумевается, что поддержание доверенного хранилища выходит за рамки проекта. К счастью, в состав вашей операционной системы, скорее всего, уже входит доверенное хранилище, которое можно использовать. Кроме того, как мы продемонстрируем в одном из следующих разделов, вы можете без особого труда построить своё собственное.

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

Просмотр доступных команд

OpenSSL — это криптографический инструментарий, состоящий из множества различных утилит. В моей версии я насчитал 46. Тот самый случай, когда уместно употребить фразу Швейцарский армейский нож криптографии, так оно и есть. Даже если вы будете использовать всего несколько утилит, вам следует ознакомиться со всеми доступными, поскольку неизвестно, что может понадобиться в будущем.

Специального ключевого слова для вызова справки не существует, однако справочный текст отображается всякий раз, когда вы вводите что-то, что OpenSSL не может распознать:

$ openssl help
openssl:Error: 'help' is an invalid command.

Standard commands
asn1parse         ca                ciphers           cms
crl               crl2pkcs7         dgst              dh
dhparam           dsa               dsaparam          ec
ecparam           enc               engine            errstr
gendh             gendsa            genpkey           genrsa
nseq              ocsp              passwd            pkcs12
pkcs7             pkcs8             pkey              pkeyparam
pkeyutl           prime             rand              req
rsa               rsautl            s_client          s_server
s_time            sess_id           smime             speed
spkac             srp               ts                verify
version           x509

Первая часть справки выводит список всех доступных утилит. Чтобы получить больше информации о конкретной утилите, используйте man с именем этой утилиты. Например, man ciphers выдаст подробную информацию о том, как настроены наборы алгоритмов шифрования (наборы шифров).

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

Message Digest commands (see the `dgst' command for more details) md4 md5 rmd160 sha sha1

И, наконец, в третьей части вы увидите список всех команд для работы с шифрами:

Cipher commands (see the `enc' command for more details)
aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb
aes-256-cbc       aes-256-ecb       base64            bf
bf-cbc            bf-cfb            bf-ecb            bf-ofb
camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb
camellia-256-cbc  camellia-256-ecb  cast              cast-cbc
cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb
des               des-cbc           des-cfb           des-ecb
des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb
des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb
des-ofb           des3              desx              rc2
rc2-40-cbc        rc2-64-cbc        rc2-cbc           rc2-cfb
rc2-ecb           rc2-ofb           rc4               rc4-40
seed              seed-cbc          seed-cfb          seed-ecb
seed-ofb          zlib

Построение доверенного хранилища

Вместе с OpenSSL не поставляется каких-либо доверенных корневых сертификатов (ещё говорят доверенное хранилище), так что если вы выполняете установку с нуля, то придётся поискать их где-то на стороне. Один из возможных вариантов — использовать доверенное хранилище, встроенное в вашу операционную систему. Обычно этот вариант хорош, но доверенные хранилища, имеющиеся в операционной системе по умолчанию, могут не всегда быть актуальны. Лучший (но более трудоёмкий) вариант — обратиться к Mozilla, которые прилагают много усилий для поддержания надёжного доверенного хранилища. Далее будет приведён реальный пример, как я получал доверенное хранилище для своего инструмента оценки в SSL Labs.

Поскольку проекты Mozilla с открытым исходным кодом, их доверенное хранилище можно найти в репозитории исходного кода:

https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt

К сожалению, их коллекция сертификатов находится в проприетарном формате, который в исходном состоянии нам бесполезен. Если у вас нет предрассудков в смысле получения этой коллекции от третьих лиц, проект Curl предоставляет регулярно обновляемую сконвертированную в формат Privacy-Enhanced Mail (PEM) версию, которую вы можете использовать сразу:

http://curl.haxx.se/docs/caextract.html

Но даже если вы скачаете коллекцию напрямую с сайта Mozilla, вам не придётся самостоятельно писать скрипт конвертации. Такие скрипты доступны на языках Perl и Go, их использование будет описано в следующих разделах.

Примечание: Если вы всё же решите написать собственный скрипт преобразования, имейте ввиду, что файл корневых сертификатов от Mozilla фактически содержит два типа сертификатов: доверенные (являющиеся частью хранилища), а также явно недоверенные. В Mozilla используют такой механизм для запрета скомпрометированных сертификатов промежуточных УЦ (например, старых сертификатов DigiNotar). Оба описанных далее инструмента работы с файлом Mozilla достаточно разборчивы и способны исключить недоверенные сертификаты в процессе конвертации.

Конвертация с использованием Perl

Проект Curl обнародовал Perl-срипт, написанный Guenter Knauf, который может быть использован для конвертации доверенного хранилища Mozilla:

https://raw.github.com/bagder/curl/master/lib/mk-ca-bundle.pl

После скачивания и запуска скрипта он извлечёт данные сертификатов из хранилища Mozilla и преобразует их в формат PEM:

$ ./mk-ca-bundle.pl
Downloading 'certdata.txt' ...
Processing  'certdata.txt' ...
Done (156 CA certs processed, 19 untrusted skipped).

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

Конвертация с использованием Go

Если вы предпочитаете язык программирования Go, обратите внимание на инструмент конвертации от Adam Langley, который можно получить на GitHub:

https://github.com/agl/extract-nss-root-certs

Для выполнения конвертации сначала скачайте сам инструмент:

$ wget https://raw.github.com/agl/extract-nss-root-certs/master/convert_mozilla_certdata.go

А потом данные сертификатов от Mozilla:

$ wget https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt --output-document certdata.txt

Наконец, переконвертируйте файл следующей командой:

$ go run convert_mozilla_certdata.go > ca-certificates
2012/06/04 09:52:29 Failed to parse certificate starting on line 23068: negative serial number 

В моём случае имел место некорректный сертификат, который не смогла обработать библиотека X.509 языка Go, но обычно конвертация проходит без эксцессов. Версии Go, начиная с версии 1.6 и выше, не должны выдавать это предупреждение, поскольку они могут обрабатывать сертификаты с отрицательными серийными номерами.

Управление ключами и сертификатами

Большинство пользователей обращаются к OpenSSL, потому что хотят настроить и запустить веб-сервер с поддержкой SSL. Этот процесс состоит из трёх этапов: (1) генерация стойкого закрытого ключа, (2) создание запроса на подписание сертификата (Certificate Signing Request, CSR) и направление его в УЦ, и (3) инсталляция предоставленного УЦ сертификата в ваш веб-сервер. Об этих (и некоторых других) этапах пойдёт речь в данном разделе.

Генерация ключа

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

Ключевой алгоритм

OpenSSL поддерживает ключи RSA, DSA и ECDSA, но не все типы безоговорочно подходят для любого сценария использования. Например, в качестве ключа веб-сервера повсеместно используется RSA, поскольку ключи DSA по существу ограничены 1024 битами (Internet Explorer не поддерживает ничего более стойкого), а ключи ECDSA ещё не получили широкой поддержки УЦ. Для SSH везде используются DSA и RSA, поскольку ECDSA может не поддерживаться всеми клиентами.

Размер ключа

Размер ключа по умолчанию может быть небезопасным, поэтому его всегда следует настраивать явно. Например, размер по умолчанию для ключей RSA всего 512 бит, что попросту небезопасно. Если в наши дни вы будете использовать на своём сервере ключ длиной 512 бит, злоумышленник, получив ваш сертификат, может простым перебором (методом "грубой силы") подобрать ваш закрытый ключ, после чего он или она сможет выдавать себя за ваш веб-сайт. В наши дни безопасными считаются ключи RSA в 2048 бит, их и нужно использовать. Также необходимо использовать ключи DSA длиной 2048 бит и не менее 256 бит для ECDSA.

Кодовая фраза

Использовать кодовую фразу (passphrase) с ключом необязательно, но настоятельно рекомендуется. Защищённые ключи можно безопасно хранить, транспортировать и создавать резервные копии. С другой стороны, такие ключи неудобны, поскольку без ввода кодовой фразы их использовать нельзя. Например, запрос на ввод кодовой фразы может возникать каждый раз, когда вы захотите перезапустить ваш веб-сервер. В большинстве случаев это либо слишком неудобно, либо создаёт неприемлемые условия для доступности сервисов. Кроме того, использование защищённых ключей в рабочей среде на самом деле не сильно повышает безопасность (если вообще повышает). Это связано с тем, что после первоначальной активации закрытые ключи хранятся в незащищённом виде в памяти программы; злоумышленник, проникнувший на сервер, может получить ключи оттуда, приложив при этом лишь немного больше усилий. Так что кодовые фразы следует рассматривать только в качестве механизма защиты закрытого ключа, пока он не установлен в рабочей среде. Другими словами, нет ничего страшного в том, чтобы в рабочей среде хранить кодовые фразы рядом с ключами. Если же в рабочей среде вам требуется повышенная безопасность, стоит рассмотреть вариант с инвестированием в аппаратные решения7.

Для генерации ключа RSA используйте команду genrsa:

$ openssl genrsa -aes128 -out fd.key 2048
Generating RSA private key, 2048 bit long modulus
....+++
...................................................................................+++
e is 65537 (0x10001)
Enter pass phrase for fd.key: ****************
Verifying - Enter pass phrase for fd.key: ****************

Здесь я указал, что ключ будет защищён с помощью AES-128. Также вы можете использовать AES-192 или AES-256 (параметры -aes192 и -aes256, соответственно), но лучше воздержаться от применения других алгоритмов (DES, 3DES и SEED).

Предупреждение: Значение e, которое вы видите в выводе, относится к открытой экспоненте (public exponent), которая по умолчанию установлена в 65537. Это так называемая короткая открытая экспонента, и она значительно повышает производительность верификации RSA. При использовании параметра -3 можно выбрать в качестве открытой экспоненты число 3 и сделать верификацию ещё быстрее. Однако в истории было несколько неприятных происшествий, связанных с использованием числа 3 в качестве открытой экспоненты, поэтому, как правило, всем рекомендуется придерживаться числа 65537, обеспечивая тем самым запас прочности с исторически доказанной эффективностью.

Закрытые ключи хранятся в так называемом PEM-формате, представляющем собой просто текст:

$ cat fd.key
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,01EC21976A463CE36E9DB59FF6AF689A

vERmFJzsLeAEDqWdXX4rNwogJp+y95uTnw+bOjWRw1+O1qgGqxQXPtH3LWDUz1Ym
mkpxmIwlSidVSUuUrrUzIL+V21EJ1W9iQ71SJoPOyzX7dYX5GCAwQm9Tsb40FhV/
[21 lines removed...]
4phGTprEnEwrffRnYrt7khQwrJhNsw6TTtthMhx/UCJdpQdaLW/TuylaJMWL1JRW
i321s5me5ej6Pr4fGccNOe7lZK+563d7v5znAx+Wo1C+F7YgF+g8LOQ8emC+6AVV
-----END RSA PRIVATE KEY-----

Закрытый ключ — это не просто набор случайных данных, как может показаться на первый взгляд. Структуру ключа можно увидеть, используя следующую команду rsa:

$ openssl rsa -text -in fd.key
Enter pass phrase for fd.key: ****************
Private-Key: (2048 bit)
modulus:
    00:9e:57:1c:c1:0f:45:47:22:58:1c:cf:2c:14:db:
    [...]
publicExponent: 65537 (0x10001)
privateExponent:
    1a:12:ee:41:3c:6a:84:14:3b:be:42:bf:57:8f:dc:
    [...]
prime1:
    00:c9:7e:82:e4:74:69:20:ab:80:15:99:7d:5e:49:
    [...]
prime2:
    00:c9:2c:30:95:3e:cc:a4:07:88:33:32:a5:b1:d7:
    [...]
exponent1:
    68:f4:5e:07:d3:df:42:a6:32:84:8d:bb:f0:d6:36:
    [...]
exponent2:
    5e:b8:00:b3:f4:9a:93:cc:bc:13:27:10:9e:f8:7e:
    [...]
coefficient:
    34:28:cf:72:e5:3f:52:b2:dd:44:56:84:ac:19:00:
    [...]
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
[...]
-----END RSA PRIVATE KEY-----

Если вам понадобится иметь отдельно только открытую часть ключа, выделить её можно следующей командой rsa:

$ openssl rsa -in fd.key -pubout -out fd-public.key
Enter pass phrase for fd.key: ****************

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

$ cat fd-public.key
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnlccwQ9FRyJYHM8sFNsY
PUHJHJzhJdwcS7kBptutf/L6OvoEAzCVHi/m0qAA4QM5BziZgnvv+FNnE3sgE5pz
iovEHJ3C959mNQmpvnedXwfcOIlbrNqdISJiP0js6mDCzYjSO1NCQoy3UpYwvwj7
0ryR1F+abARehlts/Xs/PtX3VamrljiJN6JNgFICy3ZvEhLZEKxR7oob7TnyZDrj
IHxBbqPNzeiqLCFLFPGgJPa0cH8DdovBTesvu7wr/ecsf8CYyUCdEwGkZh9DKtdU
HFa9H8tWW2mX6uwYeHCnf2HTw0E8vjtOb8oYQxlQxtL7dpFyMgrpPOoOVkZZW/P0
NQIDAQAB
-----END PUBLIC KEY-----

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

Генерация ключа DSA происходит в два этапа: на первом создаются параметры DSA, а на втором — ключ. Можно выполнять их последовательно один за другим, но мне больше нравится использовать следующие две команды как одну:

$ openssl dsaparam -genkey 2048 | openssl dsa -out dsa.key -aes128
Generating DSA parameters, 2048 bit long prime
This could take some time
[...]
read DSA key
writing DSA key
Enter PEM pass phrase: ****************
Verifying - Enter PEM pass phrase: ****************

Этот подход позволяет мне генерировать защищённый кодовой фразой ключ, не оставляя при этом никаких временных файлов (с параметрами DSA) и/или временных ключей на диске.

Для ключей ECDSA процесс аналогичен, за исключением того, что отсутствует возможность создать ключи произвольных размеров. Вместо этого для каждого ключа вы выбираете именованную кривую (named curve), которая контролирует размер ключа, а также другие параметры EC. В следующем примере создаётся 256-битный ключ ECDSA с использованием именованной кривой secp256r1:

$ openssl ecparam -genkey -name secp256r1 | openssl ec -out ec.key -aes128
using curve name prime256v1 instead of secp256r1
read EC key
writing EC key
Enter PEM pass phrase: ****************
Verifying - Enter PEM pass phrase: ****************

OpenSSL поддерживает много именованных кривых (полный список можно получить с помощью параметра -list_curves), но для ключей веб-сервера выбор ограничен лишь двумя кривыми, которые поддерживаются всеми основными браузерами: secp256r1 (в OpenSSL используется имя prime256v1) и secp384r1.

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

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

Получив закрытый ключ, вы можете приступить к созданию запроса на подписание сертификата (Certificate Signing Request, CSR). Это формализованный запрос в УЦ о подписании сертификата, в котором содержится открытый ключ объекта, запрашивающего сертификат, а также некоторая информация об этом объекте. Все эти данные будут частью будущего сертификата. CSR всегда подписывается закрытым ключом, соответствующим тому открытому ключу, который содержится в запросе.

Обычно создание CSR представляет собой интерактивный процесс, в ходе которого вам потребуется предоставить элементы уникального имени объекта сертификата. Внимательно читайте инструкции, выдаваемые инструментом openssl; если вы хотите оставить поле пустым, вы должны ввести в строку запроса символ точки (.), а не просто нажать клавишу "Ввод" (в этом случае OpenSSL заполнит соответствующее поле CSR значением по умолчанию). Согласие со значениями по умолчанию не имеет никакого смысла, когда при генерации CSR используется конфигурация OpenSSL по умолчанию (весьма распространённая практика). И наоборот, вполне резонно соглашаться со значениями по умолчанию, когда вы их сами задали, либо поменяв конфигурацию OpenSSL, либо предоставив свои собственные файлы конфигурации.

$ openssl req -new -key fd.key -out fd.csr
Enter pass phrase for fd.key: ****************
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:GB
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Feisty Duck Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:www.feistyduck.com
Email Address []:webmaster@feistyduck.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

Примечание: В соответствии с разделом 5.4.1 RFC 29858, проблемный пароль (challenge password) является необязательным полем, предназначенным для использования во время отзыва сертификата как способ идентификации исходного объекта, запросившего данный сертификат. Если этот пароль введен, он будет дословно включён в CSR и передан в УЦ. Редко можно найти УЦ, обрабатывающий данное поле; во всех инструкциях, которые я видел, рекомендуется оставить его незаполненным. Задание проблемного пароля никоим образом не повышает безопасность CSR. Кроме того, это поле не следует путать с кодовой фразой, которая является отдельной характеристикой.

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

$ openssl req -text -in fd.csr -noout
Certificate Request:
    Data:
        Version: 0 (0x0)
        Subject: C=GB, L=London, O=Feisty Duck Ltd, CN=www.feistyduck.com↩
/emailAddress=webmaster@feistyduck.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b7:fc:ca:1c:a6:c8:56:bb:a3:26:d1:df:e4:e3:
                    [ещё 16 строк...]
                    d1:57
                Exponent: 65537 (0x10001)
        Attributes:
            a0:00
    Signature Algorithm: sha1WithRSAEncryption
         a7:43:56:b2:cf:ed:c7:24:3e:36:0f:6b:88:e9:49:03:a6:91:
         [ещё 13 строк...]
         47:8b:e3:28

Создание CSR из существующего сертификата

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

$ openssl x509 -x509toreq -in fd.crt -out fd.csr -signkey fd.key

Примечание: За исключением случаев применения некоторой формы привязывания открытого ключа (public key pinning), при которых вы захотите продолжать использовать существующий ключ, лучшей практикой считается генерация нового ключа каждый раз при запросе нового сертификата. Генерация ключей — быстрая и недорогая операция, к тому же значительно снижающая ваши риски.

Автоматизированная генерация CSR

Генерация CSR не обязательно должна быть интерактивной. Используя отличный от поставляемого по умолчанию файл конфигурации OpenSSL, вы можете решить сразу две задачи: автоматизировать процесс (как объясняется в этом разделе), а также сделать некоторые вещи, которые невозможны в интерактивном режиме (мы обсудим это в последующих разделах).

Например, предположим, что вы хотите автоматизировать генерацию CSR для www.feistyduck.com. Для начала нужно создать файл fd.cnf следующего содержания:

[req]
prompt = no
distinguished_name = dn
req_extensions = ext
input_password = PASSPHRASE

[dn]
CN = www.feistyduck.com
emailAddress = webmaster@feistyduck.com
O = Feisty Duck Ltd
L = London
C = GB

[ext]
subjectAltName = DNS:www.feistyduck.com,DNS:feistyduck.com

Ну а теперь можно создать CSR прямо из командной строки:

$ openssl req -new -config fd.cnf -key fd.key -out fd.csr

Подписание своего собственного сертификата

Если вы устанавливаете TLS-сервер для внутреннего использования, вам, вероятно, нет нужды обращаться в УЦ за публично доверенным сертификатом. Гораздо проще просто использовать самоподписанный сертификат. Если вы пользуетесь браузером Firefox, при первом посещении веб-сайта можно создать исключение для вашего сертификата, после чего этот сайт будет столь же безопасен, как если бы он был защищён публично доверенным сертификатом.

Если у вас уже есть CSR, создайте сертификат следующей командой:

$ openssl x509 -req -days 365 -in fd.csr -signkey fd.key -out fd.crt
Signature ok
subject=/CN=www.feistyduck.com/emailAddress=webmaster@feistyduck.com/O=Feisty Duck Ltd↩
/L=London/C=GB
Getting Private key
Enter pass phrase for fd.key: ****************

На самом деле вам не нужно отдельно создавать CSR. С помощью следующей команды можно создать самоподписанный сертификат, имея в наличии только ключ:

$ openssl req -new -x509 -days 365 -key fd.key -out fd.crt

Чтобы не отвечать ни на какие вопросы, используйте параметр -subj для предоставления информации о субъекте сертификата в командной строке:

$ openssl req -new -x509 -days 365 -key fd.key -out fd.crt \
 -subj "/C=GB/L=London/O=Feisty Duck Ltd/CN=www.feistyduck.com"

Создание сертификата, действительного для нескольких имен хостов

По умолчанию сертификаты, созданные OpenSSL, имеют только одно поле для общепринятого имени (common name) и действительны только для одного имени хоста. По этой причине, даже если у вас есть связанные веб-сайты, вы будете вынуждены использовать отдельный сертификат для каждого из них. В данной ситуации более оправданным представляется использование одного мультидоменного сертификата. Кроме того, даже если у вас один веб-сайт, необходимо убедиться, что сертификат действителен для всех возможных путей, по которым конечные пользователи могут пытаться получить к нему доступ. На практике это означает использование как минимум двух имен, одно из которых будет с префиксом www, а второе — без него (например, www.feistyduck.com и feistyduck.com).

Существует два механизма поддержки нескольких имен хостов в сертификате. Первый — перечислить все желательные имена, используя расширение X.509, которое называется Альтернативное имя субъекта (Subject Alternative Name, SAN). Второй — использовать шаблоны подстановки (wildcard). Также можно использовать комбинацию этих двух подходов (если это более удобно). На практике, для большинства сайтов можно указать просто доменное имя и шаблон подстановки для охвата всех возможных поддоменов (например, feistyduck.com и *.feistyduck.com).

Предупреждение: Когда в сертификате содержатся альтернативные имена, все общепринятые имена игнорируются. Более новые сертификаты, выпускаемые УЦ, могут даже не содержать общепринятых имён. Поэтому нужно включать все желаемые имена хостов в список альтернативных имён.

Сначала поместите информацию расширения в отдельный текстовый файл. У меня он будет называться fd.ext. В этом файле укажите имя расширения (subjectAltName) и список желаемых имён хостов, как в данном примере:

subjectAltName = DNS:*.feistyduck.com, DNS:feistyduck.com

Затем, используя команду x509, оформите сертификат, указав файл с расширением с помощью параметра -extfile:

$ openssl x509 -req -days 365 \
-in fd.csr -signkey fd.key -out fd.crt \
-extfile fd.ext

В остальном процесс ничем не отличается от рассмотренного ранее. Но когда вы в дальнейшем исследуете сгенерированный сертификат, вы обнаружите, что он содержит расширение SAN:

 X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:*.feistyduck.com, DNS:feistyduck.com

Исследование сертификата

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

В следующем примере я использовал параметр -text для печати содержимого сертификата, а также параметр -noout, чтобы не выводить сам закодированный сертификат (что выполняется по умолчанию) и не отвлекать нас от главного:

$ openssl x509 -text -in fd.crt -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 13073330765974645413 (0xb56dcd10f11aaaa5)
    Signature Algorithm: sha1WithRSAEncryption
        Issuer: CN=www.feistyduck.com/emailAddress=webmaster@feistyduck.com, O=Feisty ↩
Duck Ltd, L=London, C=GB
        Validity
            Not Before: Jun  4 17:57:34 2012 GMT
            Not After : Jun  4 17:57:34 2013 GMT
        Subject: CN=www.feistyduck.com/emailAddress=webmaster@feistyduck.com, O=Feisty ↩
Duck Ltd, L=London, C=GB
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:b7:fc:ca:1c:a6:c8:56:bb:a3:26:d1:df:e4:e3:
                    [ещё 16 строк...]
                    d1:57
                Exponent: 65537 (0x10001)
    Signature Algorithm: sha1WithRSAEncryption
         49:70:70:41:6a:03:0f:88:1a:14:69:24:03:6a:49:10:83:20:
         [ещё 13 строк...]
         74:a1:11:86

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

Расширение Basic Constraints используется для маркировки того, что рассматриваемый сертификат является сертификатом удостоверяющего центра, у которого есть возможность подписи других сертификатов. У сертификатов, которые не являются сертификатами УЦ, это расширение будет либо опущено, либо иметь значение CA, установленное в FALSE. Это расширение является критичным, что означает, что все программы, использующие в работе сертификаты, должны понимать его значение.

X509v3 Basic Constraints: critical
    CA:FALSE

Расширения Key Usage (KU) и Extended Key Usage (EKU) ограничивают то, для чего могут быть использованы сертификаты. Если эти расширения присутствуют, то для сертификата разрешены только перечисленные варианты использования. Если эти расширения отсутствуют, значит ограничений на использование не накладывается. В примере ниже приведены типичные варианты использования для сертификата веб-сервера, который, например, нельзя использовать для подписания кода:

X509v3 Key Usage: critical
    Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
    TLS Web Server Authentication, TLS Web Client Authentication

В расширении CRL Distribution Points перечислены адреса, по которым можно найти списки отзыва сертификатов (Certificate Revocation List, CRL) удостоверяющего центра. Эта информация важна в тех случаях, когда сертификаты необходимо отозвать. CRL представляют собой подписанные УЦ списки отозванных сертификатов, регулярно публикуемые через определённые промежутки времени (например, раз в семь дней).

X509v3 CRL Distribution Points:
    Full Name:
      URI:http://crl.starfieldtech.com/sfs3-20.crl

Примечание: Возможно вы заметили, что CRL доставляются по незащищённому соединению и задумались, безопасно ли пользоваться такими списками? На самом деле всё в порядке. Поскольку каждый CRL подписывается выпускающим его УЦ, браузеры могут проверить его целостность. Фактически, если бы CRL распространялись через защищённое с помощью TLS соединение, браузеры бы могли столкнуться с проблемой курицы и яйца, пытаясь проверить статус отзыва сертификата, используемого самим сервером, распространяющим CRL!

Расширение Certificate Policies используется для указания политики, в соответствии с которой был выпущен сертификат. Например, именно здесь можно найти индикаторы extended validation (EV) (как в представленном ниже примере). Эти индикаторы представлены в форме уникальных идентификаторов объектов (OID), и они являются уникальными для выпускающего УЦ. Кроме того, это расширение часто содержит один или несколько пунктов Certificate Policy Statement (CPS), которые обычно являются либо веб-страницами, либо документами PDF.

X509v3 Certificate Policies:
    Policy: 2.16.840.1.114414.1.7.23.3
    CPS: http://certificates.starfieldtech.com/repository/

Расширение Authority Information Access (AIA) обычно содержит информацию, состоящую из двух важных частей. Во первых, в нём перечислены адреса серверов-ответчиков Online Certificate Status Protocol (OCSP) удостоверяющего центра, которые могут быть использованы для проверки статуса отзыва сертификата в режиме реального времени. Данное расширение также может содержать ссылку, по которой можно найти сертификат издателя (следующий сертификат в цепочке). В наши дни сертификаты серверов редко подписываются непосредственно доверенными корневыми сертификатами УЦ, и это означает, что администраторы сервера должны включать в свою конфигурацию один или несколько промежуточных сертификатов. Здесь легко допустить ошибку, и тогда сертификат сервера будет признан недействительным. Некоторые клиенты (например, Internet Explorer) будут использовать приведённую в данном расширении информацию для исправления неполных цепочек сертификатов, но так поступают далеко не все клиенты.

Authority Information Access:
    OCSP - URI:http://ocsp.starfieldtech.com/
    CA Issuers - URI:http://certificates.starfieldtech.com/repository/sf_intermediate.crt

Расширения Subject Key Identifier и Authority Key Identifier устанавливают уникальные идентификаторы ключа субъекта и ключа удостоверяющего центра соответственно. Значение, указанное в расширении Authority Key Identifier сертификата, должно совпадать со значением, указанным в расширении Subject Key Identifier того сертификата, которым подписан рассматриваемый сертификат. Эта информация очень полезна в процессе построения пути сертификации, когда клиент пытается найти все возможные пути от конечного сертификата (серверного) до доверенного корневого сертификата. Часто удостоверяющие центры используют один закрытый ключ с более чем одним сертификатом, и это поле позволяет программам надёжно определять, какой сертификат с каким ключом может быть сопоставлен. В реальном мире многие предоставляемые серверами цепочки сертификатов являются некорректными, но это часто остаётся незамеченным, поскольку браузеры способны находить альтернативные пути доверия.

X509v3 Subject Key Identifier:
    4A:AB:1C:C3:D3:4E:F7:5B:2B:59:71:AA:20:63:D6:C9:40:FB:14:F1
X509v3 Authority Key Identifier:
    keyid:49:4B:52:27:D1:1B:BC:F2:A1:21:6A:62:7B:51:42:7A:8A:D7:D5:56

Наконец, расширение Subject Alternative Name используется для перечисления имён хостов, для которых данный сертификат является действительным. Это расширение считается необязательным. Если оно отсутствует, клиенты возвращаются к использованию информации, предоставляемой в атрибуте Common Name (CN), являющемся частью поля Subject. Если это расширение присутствует, то содержимое поля CN в процессе валидации игнорируется.

X509v3 Subject Alternative Name:
    DNS:www.feistyduck.com, DNS:feistyduck.com

Преобразование ключей и сертификатов

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

Бинарный сертификат (DER)

Содержит сертификат X.509 в необработанном виде с использованием закодированных в DER структур ASN.1.

ASCII сертификат (сертификаты) (PEM)

Содержит сертификат DER в кодировке base64, перед которым помещается маркер -----BEGIN CERTIFICATE-----, а после — маркер -----END CERTIFICATE-----. Обычно в файле присутствует только один сертификат, хотя некоторые программы, в зависимости от контекста, допускают наличие более одного сертификата в файле. Например, в старых версиях веб-сервера Apache требовалось, чтобы сертификат сервера находился отдельно в одном файле, а все промежуточные сертификаты вместе — в другом файле.

Бинарный ключ (DER)

Содержит закрытый ключ в необработанном виде с использованием закодированных в DER структур ASN.1. OpenSSL создаёт ключи в своём собственном традиционном формате (SSLeay). Существует также альтернативный формат, называемый PKCS#8 (определён в RFC 5208), но он не получил широкого распространения. OpenSSL может преобразовывать как в формат PKCS#8, так и из него с помощью команды pkcs8.

ASCII ключ (PEM)

Содержит ключ DER в кодировке base64, иногда с дополнительными метаданными (например, указанием алгоритма, используемого для парольной защиты).

Сертификат (сертификаты) PKCS#7

Комплексный формат, предназначенный для транспортировки подписанных или зашифрованных данных, определён в RFC 2315. Файлы обычно имеют расширения .p7b и .p7c и, при необходимости, могут включать целиком цепочку сертификатов. Этот формат поддерживается Java-утилитой keytool.

Ключ и сертификат (сертификаты) PKCS#12 (PFX)

Комплексный формат, в котором может храниться в защищённом состоянии ключ сервера и полная цепочка сертификатов. Файлы обычно имеют расширения .p12 и .pfx. Этот формат обычно используется в продуктах Microsoft, но также может использоваться для клиентских сертификатов. В наши дни имя PFX используется как синоним для PKCS#12, хотя раньше (довольно давно) PFX указывал на другой формат (раннюю версию PKCS#12). Вряд ли вы сейчас встретите где-либо старую версию.

Преобразование в PEM и DER

Преобразование сертификата между форматами PEM и DER выполняется с помощью команды x509. Конвертация сертификата из PEM в DER:

$ openssl x509 -inform PEM -in fd.pem -outform DER -out fd.der

Конвертация сертификата из DER в PEM:

$ openssl x509 -inform DER -in fd.der -outform PEM -out fd.pem

Аналогичный синтаксис применяется для преобразования закрытых ключей между форматами DER и PEM, но в этом случае используются другие команды: rsa для ключей RSA, и dsa для ключей DSA.

Преобразование PKCS#12 (PFX)

Всего лишь одной команды достаточно для преобразования ключа и сертификатов, представленных в формате PEM, в PKCS#12. В следующем примере ключ (fd.key), сертификат (fd.crt) и промежуточные сертификаты (fd-chain.crt) конвертируются в один эквивалентный файл PKCS#12:

$ openssl pkcs12 -export \
    -name "My Certificate" \
    -out fd.p12 \
    -inkey fd.key \
    -in fd.crt \
    -certfile fd-chain.crt
Enter Export Password: ****************
Verifying - Enter Export Password: ****************

С обратным преобразованием не всё так просто. Можно использовать одну команду, но в этом случае вы получите всё содержимое в одном файле:

$ openssl pkcs12 -in fd.p12 -out fd.pem -nodes

Теперь вам нужно открыть полученный файл fd.pem в вашем любимом редакторе и вручную разделить на отдельные файлы с ключом, сертификатом и промежуточными сертификатами. При этом вы заметите дополнительный контент перед каждым компонентом. Например:

Bag Attributes
    localKeyID: E3 11 E4 F1 2C ED 11 66 41 1B B8 83 35 D2 DD 07 FC DE 28 76
subject=/1.3.6.1.4.1.311.60.2.1.3=GB/2.5.4.15=Private Organization/serialNumber=06694169↩
/C=GB/ST=London/L=London/O=Feisty Duck Ltd/CN=www.feistyduck.com
issuer=/C=US/ST=Arizona/L=Scottsdale/O=Starfield Technologies, Inc./OU=http:/↩
/certificates.starfieldtech.com/repository/CN=Starfield Secure Certification Authority
-----BEGIN CERTIFICATE-----
MIIF5zCCBM+gAwIBAgIHBG9JXlv9vTANBgkqhkiG9w0BAQUFADCB3DELMAkGA1UE
BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAj
[...]

Эти дополнительные метаданные очень удобны для быстрой идентификации сертификатов. Очевидно, вы должны убедиться, что основной файл сертификата содержит конечный сертификат сервера, а не что-то ещё. Кроме того, вы также должны убедиться, что промежуточные сертификаты представлены в правильном порядке, когда сертификат, с помощью которого производилось подписание, следует за подписанным сертификатом. Если вы заметите самоподписанный корневой сертификат, можете смело его удалить или сохранить в другом месте; он не должен входить в цепочку сертификатов.

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

Разделение пакета на компоненты можно переложить и на OpenSSL, но при этом придётся несколько раз вызывать команду pkcs12 (и каждый раз вводить пароль к пакету):

$ openssl pkcs12 -in fd.p12 -nocerts -out fd.key -nodes
$ openssl pkcs12 -in fd.p12 -nokeys -clcerts -out fd.crt
$ openssl pkcs12 -in fd.p12 -nokeys -cacerts -out fd-chain.crt

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

Преобразование PKCS#7

Для преобразования из PEM в PKCS#7 используйте команду crl2pkcs7:

$ openssl crl2pkcs7 -nocrl -out fd.p7b -certfile fd.crt -certfile fd-chain.crt

Для преобразования из PKCS#7 в PEM используйте команду pkcs7 с параметром -print_certs:

openssl pkcs7 -in fd.p7b -print_certs -out fd.pem

Как и в случае с преобразованием из PKCS#12, нужно будет отредактировать файл fd.pem, очистив его и разделив на желаемые компоненты.

Конфигурация

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

Выбор набора шифров

Распространённая задача в конфигурации сервера TLS — выбор того, какие наборы шифров будут поддерживаться. Для безопасной коммуникации программному обеспечению TLS необходимо определиться, какие именно криптографические примитивы использовать для достижения своих целей (например, обеспечения конфиденциальности). Это достигается путём выбора подходящего набора шифров, на основании которого принимается ряд решений о том, как будут выполняться аутентификация, обмен ключами, шифрование и другие операции. Программы, использующие в своей работе OpenSSL, обычно применяют тот же подход к настройке наборов шифров, что и OpenSSL, просто задавая те же параметры конфигурации. Например, в настройках сервера httpd от Apache конфигурация наборов шифров может выглядеть так:

SSLHonorCipherOrder On  
SSLCipherSuite "HIGH:!aNULL:@STRENGTH"

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

Создание хорошей конфигурации наборов может занять довольно много времени, при этом нужно учитывать множество деталей. Лучшим подходом в данном случае будет использование команды OpenSSL ciphers для определения того, какие именно наборы будут задействованы при выборе конкретной строки конфигурации.

Получение списка поддерживаемых наборов

Перед тем как продолжать, вам следует определиться с тем, какие наборы поддерживаются вашей инсталляцией OpenSSL. Для этого вызовите команду ciphers с параметром -v и указанием ключевых слов ALL:COMPLEMENTOFALL (очевидно, ALL в данном случае вовсе на означает "все"):

$ openssl ciphers -v 'ALL:COMPLEMENTOFALL'
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH Au=RSA   Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2 Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256)    Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3   Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA1
[ещё 106 строк...]

Совет: При использовании OpenSSL 1.0.0 или более поздней версии, вы также можете применить параметр -V (в верхнем регистре), чтобы сделать вывод ещё подробнее. В этом режиме в вывод попадут также идентификаторы наборов, которые в любом случае полезно иметь под рукой. Например, OpenSSL не всегда использует для наборов шифров имена из RFC; в таких случаях вы должны использовать эти идентификаторы для обеспечения перекрёстной проверки.

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

  1. Имя набора

  2. Минимально допустимая версия протокола

  3. Алгоритм обмена ключами

  4. Алгоритм аутентификации

  5. Алгоритм и стойкость шифрования

  6. Алгоритм MAC (обеспечения целостности)

  7. Индикатор экспортного набора

Если изменить параметр ciphers на что-то отличное от ALL:COMPLEMENTOFALL, OpenSSL покажет только те наборы, которые будут соответствовать указанной настройке. Например, вы можете запросить вывод только тех наборов шифров, которые основаны на RC4, следующей командой:

$ openssl ciphers -v 'RC4'
ECDHE-RSA-RC4-SHA    SSLv3 Kx=ECDH       Au=RSA   Enc=RC4(128) Mac=SHA1
ECDHE-ECDSA-RC4-SHA  SSLv3 Kx=ECDH       Au=ECDSA Enc=RC4(128) Mac=SHA1
AECDH-RC4-SHA        SSLv3 Kx=ECDH       Au=None  Enc=RC4(128) Mac=SHA1
ADH-RC4-MD5          SSLv3 Kx=DH         Au=None  Enc=RC4(128) Mac=MD5
ECDH-RSA-RC4-SHA     SSLv3 Kx=ECDH/RSA   Au=ECDH  Enc=RC4(128) Mac=SHA1
ECDH-ECDSA-RC4-SHA   SSLv3 Kx=ECDH/ECDSA Au=ECDH  Enc=RC4(128) Mac=SHA1
RC4-SHA              SSLv3 Kx=RSA        Au=RSA   Enc=RC4(128) Mac=SHA1
RC4-MD5              SSLv3 Kx=RSA        Au=RSA   Enc=RC4(128) Mac=MD5
PSK-RC4-SHA          SSLv3 Kx=PSK        Au=PSK   Enc=RC4(128) Mac=SHA1
EXP-ADH-RC4-MD5      SSLv3 Kx=DH(512)    Au=None  Enc=RC4(40)  Mac=MD5  export
EXP-RC4-MD5          SSLv3 Kx=RSA(512)   Au=RSA   Enc=RC4(40)  Mac=MD5  export

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

Ключевые слова

Ключевые слова набора шифров являются базовыми строительными блоками конфигурации наборов шифров. Каждое имя набора (например, RC4-SHA) является ключевым словом, с помощью которого выбирается ровно один набор. Все остальные ключевые слова отбирают группу наборов в соответствии с некоторыми критериями. Имена ключевых слов чувствительны к регистру символов. Конечно, можно было бы просто перенаправить вас на документацию OpenSSL для просмотра всего списка ключевых слов, но оказывается, что раздел по шифрам в ней не актуален: в нём не хватает нескольких последних добавлений. По этой причине я попытался задокументировать все ключевые слова в этом разделе.

Групповые ключевые слова — это сокращения для выбора часто используемых наборов шифров. Например, при наличии ключевого слова HIGH будут отобраны только очень стойкие наборы шифров.

Таблица 1.1. Групповые ключевые слова

Ключевое словоЗначение
DEFAULTСписок шифров по умолчанию. Определяется во время компиляции, для OpenSSL 1.0.0, как правило, ALL:!aNULL:!eNULL. При настройке отбора наборов шифров в приложении это ключевое слово должно быть указано первым в конфигурационной строке.
COMPLEMENTOFDEFAULTШифры, входящие в ALL, но не задействованные по умолчанию. В настоящий момент это ADH. Обратите внимание, что это правило не распространяется на шифры eNULL, которые не входят в ALL (если это необходимо, используйте COMPLEMENTOFALL).
ALLВсе наборы шифров, кроме шифров eNULL, которые должны быть включены явно.
COMPLEMENTOFALLНаборы шифров, не включённые в ALL, в настоящий момент это eNULL.
HIGHНаборы шифров "высокой" криптографической стойкости. В настоящий момент это означает наборы шифров с ключами, имеющими уровень криптостойкости более 128 бит, а также некоторые наборы шифров с уровнем криптостойкости в 128 бит.
MEDIUMНаборы шифров "средней" криптографической стойкости, в настоящий момент некоторые из них используют шифрование с уровнем криптостойкости 128 бит.
LOWНаборы шифров "низкой" криптографической стойкости. В настоящее время это те из них, в которых используются криптографические алгоритмы с уровнем криптостойкости 64 или 56 бит, за исключением экспортных наборов шифров. Небезопасные.
EXP, EXPORTЭкспортные криптографические алгоритмы. Включая алгоритмы с уровнем криптостойкости 40 и 56 бит. Небезопасные.
EXPORT40Экспортные алгоритмы с уровнем криптостойкости 40 бит. Небезопасные.
EXPORT56Экспортные алгоритмы с уровнем криптостойкости 56 бит. Небезопасные.
TLSv1, SSLv3, SSLv2Наборы шифров TLS 1.0, SSL 3 и SSL 2, соответственно.

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

Таблица 1.2. Ключевые слова алгоритмов хэширования

Ключевое словоЗначение
MD5Наборы шифров, использующие MD5. Устаревшие и небезопасные.
SHA, SHA1Наборы шифров, использующие SHA1.
SHA256 (v1.0.0+)Наборы шифров, использующие SHA256.
SHA384 (v1.0.0+)Наборы шифров, использующие SHA384.

Примечание: С помощью ключевых слов алгоритмов хэширования выбираются только те наборы шифров, которые проверяют целостность данных на уровне протокола. В TLS 1.2 появилась поддержка аутентифицированного шифрования, представляющего собой механизм, который связывает шифрование с проверкой целостности. При использовании так называемых наборов AEAD (Authenticated Encryption with Associated Data), протоколу не требуется обеспечивать дополнительную проверку целостности. По этой причине вы не сможете использовать ключевые слова алгоритмов хэширования для выбора наборов AEAD (в настоящее время это наборы, в названии которых присутствует GCM). В названиях таких наборов используются суффиксы SHA256 и SHA384, но в данном случае они указывают на хэш-функции, используемые для построения используемой с набором псевдослучайной функции (что вносит некоторую путаницу).

С помощью ключевых слов аутентификации наборы выбираются на основании тех методов аутентификации, которые они используют. Сегодня для аутентификации практически во всех публичных сертификатах используется RSA. Со временем мы, вероятно, увидим очень медленный прирост использования сертификатов Elliptic Curve (ECDSA).

Табица 1.3. Ключевые слова аутентификации

Ключевое словоЗначение
aDHНаборы шифров, фактически использующие аутентификацию DH, то есть сертификаты содержат ключи DH. (v1.0.2+)
aDSS, DSSНаборы шифров, использующие аутентификацию DSS, то есть сертификаты содержат ключи DSS.
aECDH (v1.0.0+)Наборы шифров, использующие аутентификацию ECDH.
aECDSA (v1.0.0+)Наборы шифров, использующие аутентификацию ECDSA.
aNULLНаборы шифров, не предлагающие аутентификацию. В настоящее время это анонимные алгоритмы DH. Небезопасные.
aRSAНаборы шифров, использующие аутентификацию RSA, то есть сертификаты содержат ключи RSA.
PSKНаборы шифров, использующие аутентификацию PSK (Pre-Shared Key).
SRPНаборы шифров, использующие аутентификацию SRP (Secure Remote Password).

С помощью ключевых слов обмена ключами наборы выбираются на основании алгоритма обмена ключами. Когда речь заходит о наборах с эфемерным обменом по протоколу Диффи-Хеллмана, разработчики OpenSSL непоследовательны в именовании наборов и ключевых слов. В названиях наборов эфемерные алгоритмы обмена ключами, как правило обозначаются буквой E на конце названия алгоритма (например, ECDHE-RSA-RC4-SHA и DHE-RSA-AES256-SHA), а в ключевых словах отбора буква E указывается в начале (например, EECDH и EDH). Чтобы усугубить ситуацию, в некоторых старых наборах шифров буква E ставится в начале названия алгоритма обмена ключами (например, EDH-RSA-DES-CBC-SHA).

Таблица 1.4. Ключевые слова обмена ключами

Ключевое словоЗначение
ADHНаборы шифров с анонимным обменом DH. Небезопасные.
AECDH (v1.0.0+)Наборы шифров с анонимным обменом ECDH. Небезопасные.
DHНаборы шифров, использующие обмен DH (в том числе эфемерный и анонимный обмен DH).
ECDH (v1.0.0+)Наборы шифров, использующие обмен ECDH (в том числе эфемерный и анонимный обмен ECDH).
EDH (v1.0.0+)Наборы шифров, использующие эфемерное согласование ключей DH.
EECDH (v1.0.0+)Наборы шифров, использующие эфемерный обмен ECDH.
kECDH (v1.0.0+)Наборы шифров, использующие эфемерное согласование ключей ECDH.
kEDHНаборы шифров, использующие эфемерное согласование ключей DH (включая анонимный обмен DH).
kEECDH (v1.0.0+)Наборы шифров, использующие эфемерное согласование ключей ECDH (включая анонимный обмен ECDH).
kRSA, RSAНаборы шифров, использующие алгоритм обмена ключами RSA.

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

Таблица 1.5. Ключевые слова шифров

Ключевое словоЗначение
3DESНаборы шифров, использующие Triple DES. Устаревшие и небезопасные.
AESНаборы шифров, использующие AES.
AESGCM (v1.0.0+)Наборы шифров, использующие AES GCM.
CAMELLIAНаборы шифров, использующие Camellia. Устаревшие.
DESНаборы шифров, использующие Single DES. Устаревшие и небезопасные.
eNULL, NULLНаборы шифров не использующие шифрование. Небезопасные.
IDEAНаборы шифров, использующие IDEA. Устаревшие.
RC2Наборы шифров, использующие RC2. Устаревшие и небезопасные.
RC4Наборы шифров, использующие RC4. Небезопасные.
SEEDНаборы шифров, использующие SEED. Устаревшие.

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

Таблица 1.6. Прочие ключевые слова

Ключевое словоЗначение
@STRENGTHСортирует текущий список наборов шифров в порядке длины ключа алгоритма шифрования..
aGOSTНаборы шифров, использующие аутентификацию GOST R 34.10 (как 2001, так и 94). Для работы требуется модуль обеспечения совместимости с ГОСТ.
aGOST01Наборы шифров, использующие аутентификацию GOST R 34.10-2001.
aGOST94Наборы шифров, использующие аутентификацию GOST R 34.10-94. Устаревшие. Вместо них используйте GOST R 34.10-2001.
kGOSTНаборы шифров, использующие обмен ключами VKO 34.10, определённый в RFC 4357.
GOST94Наборы шифров, использующие HMAC, основанный на GOST R 34.11-94.
GOST89MACНаборы шифров, использующие GOST 28147-89 MAC вместо HMAC.

Комбинирование ключевых слов

Чаще всего вы будете использовать ключевые слова по отдельности, но также возможно комбинировать их, чтобы отбирать только те наборы, которые удовлетворяют сразу нескольким требованиям. Это делается путём соединения двух или более ключевых слов символом +. В следующем примере мы выбираем наборы, использующие RC4 и SHA:

$ openssl ciphers -v 'RC4+SHA'
ECDHE-RSA-RC4-SHA    SSLv3 Kx=ECDH       Au=RSA   Enc=RC4(128) Mac=SHA1
ECDHE-ECDSA-RC4-SHA  SSLv3 Kx=ECDH       Au=ECDSA Enc=RC4(128) Mac=SHA1
AECDH-RC4-SHA        SSLv3 Kx=ECDH       Au=None  Enc=RC4(128) Mac=SHA1
ECDH-RSA-RC4-SHA     SSLv3 Kx=ECDH/RSA   Au=ECDH  Enc=RC4(128) Mac=SHA1
ECDH-ECDSA-RC4-SHA   SSLv3 Kx=ECDH/ECDSA Au=ECDH  Enc=RC4(128) Mac=SHA1
RC4-SHA              SSLv3 Kx=RSA        Au=RSA   Enc=RC4(128) Mac=SHA1
PSK-RC4-SHA          SSLv3 Kx=PSK        Au=PSK   Enc=RC4(128) Mac=SHA1

Построение списков наборов шифров

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

$ openssl ciphers -v 'RC4:AES'

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

$ openssl ciphers -v 'RC4 AES'

Модификаторы ключевых слов

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

Добавление в конец

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

Удаление (-)

Удаляет все соответствующие наборы из текущего списка. Потенциально позволяет некоторым другим ключевым словам повторно ввести эти наборы позже.

Удаление навсегда (!)

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

Перемещение в конец (+)

Перемещает все соответствующие наборы в конец текущего списка. Работает только для тех наборов, которые уже в списке; новые наборы в список не добавляются. Этот модификатор полезен, если вы хотите, чтобы некоторые более слабые наборы были включены, но отдаёте предпочтение более сильным. Например, строка RC4:+MD5 включает все наборы RC4, но помещает те из них, которые основаны на MD5, в конец списка.

Сортировка

Ключевое слово @STRENGTH отличается от других ключевых слов (не зря же в его названии есть символ @): оно не добавляет и не удаляет никаких наборов, но сортирует их в порядке уменьшения стойкости шифра. Автоматическая сортировка — интересная идея, но она имеет смысл только в идеальном мире, в котором наборы шифров можно было бы сопоставить лишь по формальному уровню криптостойкости шифра.

Возьмём для примера следующую конфигурацию наборов шифров:

$ openssl ciphers -v 'DES-CBC-SHA DES-CBC3-SHA RC4-SHA AES256-SHA @STRENGTH'
AES256-SHA                     SSLv3   Kx=RSA  Au=RSA   Enc=AES(256)    Mac=SHA1
DES-CBC3-SHA                   SSLv3   Kx=RSA  Au=RSA   Enc=3DES(168)   Mac=SHA1
RC4-SHA                        SSLv3   Kx=RSA  Au=RSA   Enc=RC4(128)    Mac=SHA1
DES-CBC-SHA                    SSLv3   Kx=RSA  Au=RSA   Enc=DES(56)     Mac=SHA1

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

Обработка ошибок

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

$ openssl ciphers -v '@HIGH'
Error in cipher list
140460843755168:error:140E6118:SSL routines:SSL_CIPHER_PROCESS_RULESTR:invalid ↩
command:ssl_ciph.c:1317:

Вывод, конечно, загадочный, но в нём содержится сообщение об ошибке.

Другая возможная ошибка состоит в том, что вы получите пустой список наборов шифров. В этом случае вывод будет примерно такой:

$ openssl ciphers -v 'SHA512'
Error in cipher list
140202299557536:error:1410D0B9:SSL routines:SSL_CTX_set_cipher_list:no cipher match:ssl↩
_lib.c:1312:

Собираем всё вместе

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

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

  1. Используются только стойкие шифры с фактическим уровнем криптостойкости 128 бит и выше (это исключает 3DES).

  2. Используются только наборы, обеспечивающие строгую аутентификацию (это исключает анонимные и экспортные наборы).

  3. Не используются любые наборы, полагающиеся на уязвимые исходные алгоритмы (например, MD5).

  4. Реализуется надежная поддержка прямой секретности, независимо от того, какие ключи и протоколы применяются. С этим требованием связано небольшое снижение производительности, потому что не будет возможности использовать быстрый обмен ключами RSA. Чтобы минимизировать это снижение, мы будем отдавать приоритет ECDHE, которые значительно быстрее, чем DHE.

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

  6. При работе с клиентами TLS 1.2 предпочтение отдаётся наборам AES GCM, обеспечивающим наилучший уровень безопасности, который только может предложить TLS.

  7. Поскольку недавно выяснилось, что шифры RC4 слабее, чем это считалось ранее11, поместим их в конец нашего списка. Это почти также эффективно, как и полное их отключение. Хотя BEAST всё ещё может быть проблемой в некоторых ситуациях, предполагается, что о минимизации этой атаки позаботились на стороне клиента.

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

Слабые наборы могут быть идентифицированы следующими ключевыми словами:

Для уменьшения количества выводимых наборов я собираюсь полностью устранить все наборы DSA, PSK, SRP и ECDH, поскольку они используются крайне редко. Также я удалю шифры IDEA и SEED, которые устарели, но все еще могут поддерживаться OpenSSL. В своей конфигурации я также не буду использовать CAMELLIA, потому что он медленнее и не так хорошо поддерживается, как AES (например, на практике нет вариантов GCM или ECDHE).

!aNULL !eNULL !LOW !3DES !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !IDEA !SEED

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

kEECDH kEDH !aNULL !eNULL !LOW !3DES !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !IDEA ↩
!SEED

Если протестировать эту конфигурацию, обнаружится, что наборы RSA перечислены первыми, хотя мы хотели бы видеть первыми ECDSA:

ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH Au=RSA   Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2 Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA384
ECDHE-ECDSA-AES256-SHA384      TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256)    Mac=SHA384
ECDHE-RSA-AES256-SHA           SSLv3   Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA1
ECDHE-ECDSA-AES256-SHA         SSLv3   Kx=ECDH Au=ECDSA Enc=AES(256)    Mac=SHA1
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH Au=RSA   Enc=AESGCM(128) Mac=AEAD
[...]

Чтобы это исправить, поместим kEECDH+ECDSA в начало конфигурации:

kEECDH+ECDSA kEECDH kEDH !aNULL !eNULL !LOW !3DES !MD5 !EXP !DSS !PSK !SRP !kECDH ↩
!CAMELLIA !IDEA !SEED

Следующая проблема — то, что более старые наборы (SSL 3) перемешаны с более новыми (TLS 1.2). Для обеспечения максимальной безопасности хотелось бы, чтобы клиенты, поддерживающие TLS 1.2, всегда согласовывали именно TLS 1.2. Чтобы переместить более старые наборы в конец списка, я использую ключевое слово +SHA (наборы TLS 1.2 всегда используют либо SHA256, либо SHA384, то есть под это условие они не попадут):

kEECDH+ECDSA kEECDH kEDH +SHA !aNULL !eNULL !LOW !3DES !MD5 !EXP !DSS !PSK !SRP !kECDH ↩
!CAMELLIA !IDEA !SEED

В таком виде конфигурация практически полностью отвечает нашим требованиям. Осталось поместить оставшиеся ещё неохваченными безопасные наборы в конец списка; сделаем это с помощью ключевого слова HIGH. Кроме того, нужно удостовериться, что наборы RC4 являются последними; с помощью ключевого слова +RC4 мы переместим уже существующие наборы RC4 в конец списка, а с помощью ключевого слова RC4 добавим туда же ещё незадействованные наборы RC4:

kEECDH+ECDSA kEECDH kEDH HIGH +SHA +RC4 RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !DSS ↩
!PSK !SRP !kECDH !CAMELLIA !IDEA !SEED

Давайте рассмотрим окончательный результат, который состоит из 28 наборов. В первой группе у нас наборы TLS 1.2:

ECDHE-ECDSA-AES256-GCM-SHA384  TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(256) Mac=AEAD
ECDHE-ECDSA-AES256-SHA384      TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(256)    Mac=SHA384
ECDHE-ECDSA-AES128-GCM-SHA256  TLSv1.2 Kx=ECDH Au=ECDSA Enc=AESGCM(128) Mac=AEAD
ECDHE-ECDSA-AES128-SHA256      TLSv1.2 Kx=ECDH Au=ECDSA Enc=AES(128)    Mac=SHA256
ECDHE-RSA-AES256-GCM-SHA384    TLSv1.2 Kx=ECDH Au=RSA   Enc=AESGCM(256) Mac=AEAD
ECDHE-RSA-AES256-SHA384        TLSv1.2 Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA384
ECDHE-RSA-AES128-GCM-SHA256    TLSv1.2 Kx=ECDH Au=RSA   Enc=AESGCM(128) Mac=AEAD
ECDHE-RSA-AES128-SHA256        TLSv1.2 Kx=ECDH Au=RSA   Enc=AES(128)    Mac=SHA256
DHE-RSA-AES256-GCM-SHA384      TLSv1.2 Kx=DH   Au=RSA   Enc=AESGCM(256) Mac=AEAD
DHE-RSA-AES256-SHA256          TLSv1.2 Kx=DH   Au=RSA   Enc=AES(256)    Mac=SHA256
DHE-RSA-AES128-GCM-SHA256      TLSv1.2 Kx=DH   Au=RSA   Enc=AESGCM(128) Mac=AEAD
DHE-RSA-AES128-SHA256          TLSv1.2 Kx=DH   Au=RSA   Enc=AES(128)    Mac=SHA256
AES256-GCM-SHA384              TLSv1.2 Kx=RSA  Au=RSA   Enc=AESGCM(256) Mac=AEAD
AES256-SHA256                  TLSv1.2 Kx=RSA  Au=RSA   Enc=AES(256)    Mac=SHA256
AES128-GCM-SHA256              TLSv1.2 Kx=RSA  Au=RSA   Enc=AESGCM(128) Mac=AEAD
AES128-SHA256                  TLSv1.2 Kx=RSA  Au=RSA   Enc=AES(128)    Mac=SHA256

Сначала идут наборы ECDHE, затем — DHE, а за ними уже все остальные наборы TLS 1.2. В каждой из подгрупп приоритет имеют ECDSA и GCM.

Во второй группе представлены наборы, которые будут использоваться клиентами TLS 1.0. Приоритеты здесь такие же, как и в первой группе:

ECDHE-ECDSA-AES256-SHA         SSLv3   Kx=ECDH Au=ECDSA Enc=AES(256)    Mac=SHA1
ECDHE-ECDSA-AES128-SHA         SSLv3   Kx=ECDH Au=ECDSA Enc=AES(128)    Mac=SHA1
ECDHE-RSA-AES256-SHA           SSLv3   Kx=ECDH Au=RSA   Enc=AES(256)    Mac=SHA1
ECDHE-RSA-AES128-SHA           SSLv3   Kx=ECDH Au=RSA   Enc=AES(128)    Mac=SHA1
DHE-RSA-AES256-SHA             SSLv3   Kx=DH   Au=RSA   Enc=AES(256)    Mac=SHA1
DHE-RSA-AES128-SHA             SSLv3   Kx=DH   Au=RSA   Enc=AES(128)    Mac=SHA1
DHE-RSA-SEED-SHA               SSLv3   Kx=DH   Au=RSA   Enc=SEED(128  ) Mac=SHA1
AES256-SHA                     SSLv3   Kx=RSA  Au=RSA   Enc=AES(256)    Mac=SHA1
AES128-SHA                     SSLv3   Kx=RSA  Au=RSA   Enc=AES(128)    Mac=SHA1

И наконец, наборы RC4 представлены в конце списка:

ECDHE-ECDSA-RC4-SHA            SSLv3   Kx=ECDH Au=ECDSA Enc=RC4(128)    Mac=SHA1
ECDHE-RSA-RC4-SHA              SSLv3   Kx=ECDH Au=RSA   Enc=RC4(128)    Mac=SHA1
RC4-SHA                        SSLv3   Kx=RSA  Au=RSA   Enc=RC4(128)    Mac=SHA1

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

Принципы проектирования для всех конфигураций здесь, в основном, те же, что и в предыдущем разделе, но я собираюсь сделать пару изменений для достижения лучшей производительности. Во-первых, я собираюсь поместить наборы, шифры которых имеют уровень криптостойкости 128 бит, в начало списка. Хотя 256-битные наборы обеспечивают некоторое повышение безопасности, для большинства сайтов оно не имеет смысла и сопровождается снижением производительности. Во-вторых, я собираюсь отдать предпочтение наборам с HMAC-SHA, а не с HMAC-SHA256 и HMAC-SHA384, которые гораздо медленнее и также не обеспечивают значительного повышения безопасности.

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

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

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

ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA
ECDHE-ECDSA-AES128-SHA256
ECDHE-ECDSA-AES256-SHA384
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-AES128-SHA
ECDHE-RSA-AES256-SHA
ECDHE-RSA-AES128-SHA256
ECDHE-RSA-AES256-SHA384
DHE-RSA-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA
DHE-RSA-AES128-SHA256
DHE-RSA-AES256-SHA256

Примечание: Начиная с ветки 1.1.x, OpenSSL поддерживает наборы ChaCha20/Poly130512. Эти наборы с проверкой аутентификации (например, ECDHE-ECDSA-CHACHA20-POLY1305 и ECDHE-RSA-CHACHA20-POLY1305) обеспечивают высокий уровень безопасности и альтернативу наборам AES. Сообщается, что они обеспечивают более высокую производительность для пользователей мобильных устройств, но наборы AES GCM всё ещё работают быстрее на современных настольных компьютерах, где доступно аппаратное ускорение. Поместите их в начало списка или сразу за вашими наборами GCM, в зависимости от того, какая часть вашей аудитории использует мобильные устройства. Во время написания этого материала OpenSSL ещё не поддерживал одинаковый уровень предпочтения наборов (в отличие, например, от BoringSSL), что позволяло бы клиенту самому выбрать между наборами AES-GCM и ChaCha20.

В этой конфигурации используются только наборы с поддержкой прямой секретности и обеспечением стойкого шифрования. Большинство современных браузеров и других клиентов смогут установить соединение, но некоторые очень старые клиенты, возможно, нет. Например, старые версии Internet Explorer, работающие на Windows XP, будут выдавать ошибку.

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

EDH-RSA-DES-CBC3-SHA
AES128-SHA
AES256-SHA
DES-CBC3-SHA
ECDHE-RSA-RC4-SHA
RC4-SHA

Большинство из этих старых наборов используют обмен ключами RSA, а значит не обеспечивают прямую секретность. Предпочтение отдаётся шифрам AES, но 3DES и (небезопасные) RC4 также поддерживаются для максимальной совместимости с как можно большим числом клиентов. Если нельзя обойтись без использования RC4, предпочтение должно отдаваться наборам с ECDHE, обеспечивающим прямую секретность.

Производительность

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

Если выполнить команду speed без параметров, OpenSSL сгенерирует большое количество выводных данных, но полезного в них будет немного. Лучше тестировать только те алгоритмы, которые вас непосредственно интересуют. Например, для защиты веб-сервера вам могут понадобиться алгоритмы RC4, AES, RSA, ECDH и SHA:

$ openssl speed rc4 aes rsa ecdh sha

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

OpenSSL 0.9.8k 25 Mar 2009
built on: Wed May 23 00:02:00 UTC 2012
options:bn(64,64) md2(int) rc4(ptr,char) des(idx,cisc,16,int) aes(partial) ↩
blowfish(ptr2)
compiler: cc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN ↩
-DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -O3 -Wa,--noexecstack -g -Wall -DMD32_REG_T=int ↩
-DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM
available timing options: TIMES TIMEB HZ=100 [sysconf value]
timing function used: times
The 'numbers' are in 1000s of bytes per second processed.

Во второй части приводятся результаты тестирования симметричной криптографии (хэш-функций и криптографических алгоритмов):

type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
sha1             29275.44k    85281.86k   192290.28k   280526.68k   327553.12k
rc4             160087.81k   172435.03k   174264.75k   176521.50k   176700.62k
aes-128 cbc      90345.06k   140108.84k   170027.92k   179704.12k   182388.44k
aes-192 cbc     104770.95k   134601.12k   148900.05k   152662.30k   153941.11k
aes-256 cbc      95868.62k   116430.41k   124498.19k   127007.85k   127430.81k
sha256           23354.37k    54220.61k    99784.35k   126494.48k   138266.71k
sha512           16022.98k    64657.88k   113304.06k   178301.77k   214539.99k

Наконец, в третьей части результаты тестирования асимметричной криптографии:

                  sign    verify    sign/s verify/s
rsa  512 bits 0.000120s 0.000011s   8324.9  90730.0
rsa 1024 bits 0.000569s 0.000031s   1757.0  31897.1
rsa 2048 bits 0.003606s 0.000102s    277.3   9762.0
rsa 4096 bits 0.024072s 0.000376s     41.5   2657.4
                              op      op/s
 160 bit ecdh (secp160r1)   0.0003s   2890.2
 192 bit ecdh (nistp192)   0.0006s   1702.9
 224 bit ecdh (nistp224)   0.0006s   1743.5
 256 bit ecdh (nistp256)   0.0007s   1513.3
 384 bit ecdh (nistp384)   0.0015s    689.6
 521 bit ecdh (nistp521)   0.0029s    340.3
 163 bit ecdh (nistk163)   0.0009s   1126.2
 233 bit ecdh (nistk233)   0.0012s    818.5
 283 bit ecdh (nistk283)   0.0028s    360.2
 409 bit ecdh (nistk409)   0.0060s    166.3
 571 bit ecdh (nistk571)   0.0130s     76.8
 163 bit ecdh (nistb163)   0.0009s   1061.3
 233 bit ecdh (nistb233)   0.0013s    755.2
 283 bit ecdh (nistb283)   0.0030s    329.4
 409 bit ecdh (nistb409)   0.0067s    149.7
 571 bit ecdh (nistb571)   0.0146s     68.4

Чем может быть полезен вывод этой команды? Вы можете сравнить как опции компиляции влияют на скорость, или насколько отличается скорость работы разных версий OpenSSL на одной платформе. Например, предыдущие результаты получены на реальном сервере с использованием OpenSSL 0.9.8k (с исправлениями от поставщика дистрибутива). Я планирую перейти на OpenSSL 1.0.1h, поскольку хочу иметь поддержку TLS 1.1 и TLS 1.2; окажет ли это какое-либо влияние на производительность? Для тестирования я скачал и скомпилировал OpenSSL 1.0.1h. Давайте посмотрим:

$ ./openssl-1.0.1h speed rsa
[...]
OpenSSL 1.0.1h 5 Jun 2014
built on: Thu Jul  3 18:30:06 BST 2014
options:bn(64,64) rc4(8x,int) des(idx,cisc,16,int) aes(partial) idea(int) blowfish(idx)
compiler: gcc -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H ↩
-Wa,--noexecstack -m64 -DL_ENDIAN -DTERMIO -O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN↩
_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512↩
_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM
                  sign    verify    sign/s verify/s
rsa  512 bits 0.000102s 0.000008s   9818.0 133081.7
rsa 1024 bits 0.000326s 0.000020s   3067.2  50086.9
rsa 2048 bits 0.002209s 0.000068s    452.8  14693.6
rsa 4096 bits 0.015748s 0.000255s     63.5   3919.4

Как видите, для моего варианта использования (2048-битный ключ RSA) OpenSSL 1.0.1h почти в два раза быстрее на этом сервере: производительность возросла в 277 до 450 электронных подписей в секунду. Это означает, что в случае обновления я получу более высокую производительность. Замечательная новость!

Использовать подобные результаты тестирования для оценки производительности будущего развертывания не совсем объективно из-за большого числа факторов, влияющих на производительность в реальной жизни. Более того, многие из этих факторов лежат за пределами TLS (например, настройки HTTP Keep-Alive, кэширование и т.д.). В лучшем случае вы можете использовать эти цифры только для приблизительной оценки.

Также нельзя упускать из виду кое-что ещё. По умолчанию команда speed будет использовать только один процесс. У большинства серверов несколько ядер процессора, поэтому для определения того, как много операций TLS поддерживается сервером в целом, нужно указать в команде speed использовать несколько экземпляров в параллельном режиме. Это достигается с помощью параметра -multi. У моего сервера четыре ядра, поэтому я собираюсь использовать такую команду:

$ openssl speed -multi 4 rsa
[...]
OpenSSL 0.9.8k 25 Mar 2009
built on: Wed May 23 00:02:00 UTC 2012
options:bn(64,64) md2(int) rc4(ptr,char) des(idx,cisc,16,int) aes(partial) ↩
blowfish(ptr2)
compiler: cc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN ↩
-DHAVE_DLFCN_H -m64 -DL_ENDIAN -DTERMIO -O3 -Wa,--noexecstack -g -Wall -DMD32_REG_T=int ↩
-DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM
available timing options: TIMES TIMEB HZ=100 [sysconf value]
timing function used:
                  sign    verify    sign/s verify/s
rsa  512 bits 0.000030s 0.000003s  33264.5 363636.4
rsa 1024 bits 0.000143s 0.000008s   6977.9 125000.0
rsa 2048 bits 0.000917s 0.000027s   1090.7  37068.1
rsa 4096 bits 0.006123s 0.000094s    163.3  10652.6

Как и ожидалось, производительность возросла почти в четыре раза (я опять сужу по количеству сформированных электронных подписей RSA в секунду, поскольку эта выполняемая на сервере криптографическая операция потребляет больше всего ресурсов процессора и всегда является самым узким местом). Результат в 1090 подписей в секунду говорит о том, что этот сервер может обработать около 1000 новых TLS-соединений в секунду. В моем случае этого достаточно, даже с хорошим запасом прочности. Поскольку у меня на сервере также включено возобновление сеанса, я уверен, что он сможет поддерживать более 1000 TLS-соединений в секунду. Надеюсь, что когда-нибудь на этом сервере будет столько трафика, чтобы мне пришлось беспокоиться о производительности TLS.

Другая причина, по которой вам не следует слишком уж доверять выводу команды speed, — то, что по умолчанию она не использует наиболее быстрые из доступных реализаций шифров. В некотором смысле, вывод по умолчанию не отражает истинного состояния дел. Например, на серверах, поддерживающих установку инструкции AES-NI для ускорения вычислений AES, эта функция не будет использоваться по умолчанию при тестировании:

$ openssl speed aes-128-cbc
[...]
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128 cbc      67546.70k    74183.00k    69278.82k   155942.87k   156486.38k

Для активации аппаратного ускорения нужно использовать параметр командной строки -evp:

$ openssl speed -evp aes-128-cbc
[...]
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-cbc     188523.36k   223595.37k   229763.58k   203658.58k   206452.14k

Создание частного удостоверяющего центра

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

Образовательный аспект настройки частного УЦ — главная причина, по которой я бы рекомендовал этим заняться, но есть и другие. УЦ на базе OpenSSL, каким бы кустарным на первый взгляд он ни казался, вполне может удовлетворить потребности отдельного человека или небольшой группы. К примеру, в экспериментальной среде значительно лучше использовать частный УЦ, чем повсеместно пользоваться самоподписанными сертификатами. Кроме того, клиентские сертификаты, служащие для обеспечения двухфакторной аутентификации, могут значительно повысить безопасность ваших конфиденциальных веб-приложений.

Самая большая трудность в запуске частного УЦ состоит даже не в качественной настройке всего, а в обеспечении безопасности инфраструктуры. Например, корневой ключ должен храниться автономно (оффлайн), поскольку безопасность всего УЦ зависит от его конфиденциальности. С другой стороны, CRL и сертификаты ответчиков OCSP должны регулярно обновляться, что невозможно без применения корневого ключа.

По ходу работы с данным разделом вы создадите два конфигурационных файла: один для управления корневым УЦ (root-ca.conf), а другой для управления подчинённым УЦ (sub-ca.conf). Их можно создавать с нуля, следуя представленным здесь инструкциям, а можно скачать шаблоны этих конфигурационных файлов из моего GitHub-репозитория13. В последнем случае вы сэкономите какое-то время, однако при ручном составлении понимание процесса будет лучше.

Основной функционал и ограничения

Далее в этом разделе мы будем создавать частный УЦ, аналогичный по своей структуре публичным УЦ. Будет создан один корневой УЦ, от которого можно будет создавать подчинённые УЦ. Информацию об отзыве сертификатов мы будем предоставлять через CRL и ответчики OCSP. Для поддержания автономности корневого УЦ у ответчиков OCSP будут свои собственные идентификационные сущности. Это не самый простой частный удостоверяющий центр, который можно было бы организовать, но зато его защита будет построена правильным образом. В качестве бонуса мы сделаем подчинённый УЦ технически ограниченным, то есть ему будет разрешено выдавать сертификаты только для разрешённых имён хостов.

По окончании развёртывания УЦ корневой сертификат должен быть безопасно распространён среди всех предполагаемых клиентов. После этого можно начинать выпуск клиентских и серверных сертификатов. Основным ограничением данного варианта установки будет то, что ответчик OCSP предназначен главным образом для тестирования и не рассчитан на высокие нагрузки.

Создание корневого УЦ

Создание нового УЦ состоит из нескольких этапов: настройка, создание структуры каталогов и инициализация значимых для УЦ файлов, и, наконец, генерация корневых ключа и сертификата. В данном разделе мы рассмотрим весь этот процесс, а также основные операции, выполняемые в рамках УЦ.

Конфигурация корневого УЦ

Перед тем, как мы сможем приступить к собственно созданию УЦ, нам необходимо подготовить конфигурационный файл (root-ca.conf), в котором мы укажем OpenSSL, что же именно хотим получить в итоге. Большую часть времени (при повседневном использовании OpenSSL) подобные файлы конфигурации не требуются, однако для выполнения сложных операций, таких как создание корневого УЦ, они необходимы. Конфигурационные файлы OpenSSL весьма мощные; прежде чем продолжать, есть смысл ознакомиться с их возможностями (выполните man config в командной строке).

Первая часть конфигурационного файла содержит основную информацию об УЦ, такую как имя и базовый URL, а также компоненты отличительного имени УЦ. Из-за гибкости синтаксиса, эту информацию нужно предоставить лишь один раз:

[default]
name                    = root-ca
domain_suffix           = example.com
aia_url                 = http://$name.$domain_suffix/$name.crt
crl_url                 = http://$name.$domain_suffix/$name.crl
ocsp_url                = http://ocsp.$name.$domain_suffix:9080
default_ca              = ca_default
name_opt                = utf8,esc_ctrl,multiline,lname,align

[ca_dn]
countryName             = "GB"
organizationName        = "Example"
commonName              = "Root CA"

Вторая часть файла напрямую контролирует работу УЦ. Для получения полной информации о каждом параметре обратитесь к документации по команде ca (выполните man ca в командной строке). Большинство настроек говорят сами за себя; в основном мы сообщаем OpenSSL, где хотим хранить наши файлы. Поскольку этот корневой УЦ будет использоваться только для выпуска ключей и сертификатов подчиненных УЦ, я решил, что его сертификат будет действителен в течение 10 лет. Для формирования электронной подписи по умолчанию будет использован безопасный алгоритм хэширования SHA256.

Политика по умолчанию (policy_c_o_match) сконфигурирована таким образом, что все сертификаты, издаваемые данным УЦ, будут иметь те же значения в полях countryName и organizationName, что и в сертификате самого УЦ. Обычно в публичных УЦ так не делается, но для частного УЦ это вполне приемлемый вариант:

[ca_default]
home                    = .
database                = $home/db/index
serial                  = $home/db/serial
crlnumber               = $home/db/crlnumber
certificate             = $home/$name.crt
private_key             = $home/private/$name.key
RANDFILE                = $home/private/random
new_certs_dir           = $home/certs
unique_subject          = no
copy_extensions         = none
default_days            = 3650
default_crl_days        = 365
default_md              = sha256
policy                  = policy_c_o_match

[policy_c_o_match]
countryName             = match
stateOrProvinceName     = optional
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

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

[req]
default_bits            = 4096
encrypt_key             = yes
default_md              = sha256
utf8                    = yes
string_mask             = utf8only
prompt                  = no
distinguished_name      = ca_dn
req_extensions          = ca_ext

[ca_ext]
basicConstraints        = critical,CA:true
keyUsage                = critical,keyCertSign,cRLSign
subjectKeyIdentifier    = hash

В четвёртой части конфигурационного файла содержится информация, которая будет использоваться при создании сертификатов, издаваемых корневым удостоверяющим центром. Все эти сертификаты будут сертификатами УЦ (на что указывает содержимое расширения basicConstraints), но мы также задаём pathlen в ноль, а это означает, что создание дальнейших подчинённых УЦ (более глубокой вложенности) не допускается.

Все наши подчинённые УЦ будут ограничены в том смысле, что выданные ими сертификаты будут действительны только для определённых подмножеств доменных имён и только для ограниченного использования. Во-первых, в расширении extendedKeyUsage заданы только значения clientAuth и serverAuth, то есть ключи могут быть использованы только для аутентификации клиента и сервера TLS. Во-вторых, расширение nameConstraints ограничивает разрешённые имена хостов только значениями в пределах доменных имён example.com и example.org. В теории, такая настройка позволяет вам передать управление подчинёнными УЦ кому-то ещё, но при этом оставаться уверенным, что они не смогут выдавать сертификаты для произвольных имён хостов. При желании можно ограничить каждый подчинённый УЦ своим строго определённым (возможно, небольшим) пространством доменных имён. Требование об исключении двух диапазонов IP-адресов продиктовано "Базовыми требованиями" CA/Browser Forum14, в которых имеется определение технически ограниченного подчинённого УЦ.

На практике, установление ограничений на имена хостов работает не в полной мере, поскольку в настоящее время некоторые крупные платформы не распознают расширение nameConstraints. Если вы пометите это расширение как критичное, такие платформы будут отклонять ваши сертификаты. Этих проблем не возникнет, если вы не станете помечать расширение как критичное (как в нашем примере), но тогда некоторые другие платформы не будут его применять.

[sub_ca_ext]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical,CA:true,pathlen:0
crlDistributionPoints   = @crl_info
extendedKeyUsage        = clientAuth,serverAuth
keyUsage                = critical,keyCertSign,cRLSign
nameConstraints         = @name_constraints
subjectKeyIdentifier    = hash

[crl_info]
URI.0                   = $crl_url

[issuer_info]
caIssuers;URI.0         = $aia_url
OCSP;URI.0              = $ocsp_url

[name_constraints]
permitted;DNS.0=example.com
permitted;DNS.1=example.org
excluded;IP.0=0.0.0.0/0.0.0.0
excluded;IP.1=0:0:0:0:0:0:0:0/0:0:0:0:0:0:0:0

В финальной пятой части нашего файла указываются расширения, которые будут использоваться с сертификатом для подписи ответов OCSP. Чтобы иметь возможность запустить ответчик OCSP, мы генерируем специальный ключ (и сертификат) и делегируем ему возможность подписи OCSP. Как можно установить из расширений, этот сертификат не будет сертификатом УЦ:

[ocsp_ext]
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical,CA:false
extendedKeyUsage        = OCSPSigning
keyUsage                = critical,digitalSignature
subjectKeyIdentifier    = hash

Структура каталогов корневого УЦ

На следующем шаге мы создаём определённую в предыдущем разделе структуру каталогов и инициализируем некоторые файлы, которые будут использованы при выполнении операций УЦ:

$ mkdir root-ca
$ cd root-ca
$ mkdir certs db private
$ chmod 700 private
$ touch db/index
$ openssl rand -hex 16  > db/serial
$ echo 1001 > db/crlnumber

Будут использоваться следующие поддиректории:

certs/

Хранилище сертификатов; новые сертификаты будут размещаться здесь по мере их выдачи.

db/

В данной директории находится база данных сертификатов (index), а также файлы, в которых хранятся серийные номера следующих сертификата и CRL. По мере необходимости OpenSSL будет создавать здесь дополнительные файлы.

private/

В этой директории будут храниться закрытые ключи, один для корневого УЦ и ещё один для ответчика OCSP. Важно, чтобы у других пользователей не было доступа к этой директории. На самом деле, если вы собираетесь серьёзно относиться к этому УЦ, на машине, где хранятся корневые материалы, должно быть минимально возможное количество учётных записей пользователей.

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

Генерация корневого УЦ

Мы разобьём создание корневого УЦ на два этапа. На первом мы сгенерируем ключ и CSR. Вся необходимая информация будет взята из конфигурационного файла, который мы зададим в параметре -config:

$ openssl req -new \
    -config root-ca.conf \
    -out root-ca.csr \
    -keyout private/root-ca.key

На втором этапе мы создадим самоподписанный сертификат. Параметр -extensions указывает на раздел ca_ext конфигурационного файла, активирующий расширения, предназначенные для корневого удостоверяющего центра:

$ openssl ca -selfsign \
    -config root-ca.conf \
    -in root-ca.csr \
    -out root-ca.crt \
    -extensions ca_ext

Структура файла базы данных

База данных в файле db/index представляет собой обычный текстовый файл, содержащий информацию о сертификатах, по одному сертификату в каждой строке. Сразу после создания корневого УЦ в нём будет содержаться одна строка:

V    240706115345Z        1001    unknown    /C=GB/O=Example/CN=Root CA

В каждой строке шесть значений, разделённых знаками табуляции:

  1. Флаг статуса (V для валидного сертификата, R для отозванного, E сертификата с истёкшим сроком действия).

  2. Дата окончания срока действия (в формате YYMMDDHHMMSSZ).

  3. Дата отзыва, либо пустое поле, если сертификат не отозван.

  4. Серийный номер (в шестнадцатеричном формате).

  5. Расположение файла или unknown, если оно неизвестно.

  6. Уникальное (отличительное) имя.

Операции, выполняемые в рамках корневого УЦ

Для генерации CRL в новом УЦ используйте параметр -gencrl команды ca:

$ openssl ca -gencrl \
    -config root-ca.conf \
    -out root-ca.crl

Для выпуска сертификата выполняйте команду ca с нужными параметрами. Важно, чтобы параметр -extensions указывал на требуемый раздел конфигурационного файла (вы же не собираетесь создать ещё один корневой удостоверяющий центр).

$ openssl ca \
    -config root-ca.conf \
    -in sub-ca.csr \
    -out sub-ca.crt \
    -extensions sub_ca_ext

Для отзыва сертификата используйте параметр -revoke команды ca; вам необходимо иметь копию сертификата, который вы хотите отозвать. Поскольку все сертификаты хранятся в директории certs/, вам нужно знать только его серийный номер. Если вы знаете уникальное имя субъекта сертификата, можно найти серийный номер в базе данных.

Выберите корректную причину отзыва и укажите её в параметре -crl_reason. Возможные варианты: unspecified, keyCompromise, CACompromise, affiliationChanged, superseded, cessationOfOperation, certificateHold и removeFromCRL.

$ openssl ca \
    -config root-ca.conf \
    -revoke certs/1002.pem \
    -crl_reason keyCompromise

Создание сертификата для подписи ответов OCSP

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

$ openssl req -new \
    -newkey rsa:2048 \
    -subj "/C=GB/O=Example/CN=OCSP Root Responder" \
    -keyout private/root-ocsp.key \
    -out root-ocsp.csr

Затем мы выпустим сертификат ответчика OCSP, используя наш корневой УЦ. В качестве значения параметра -extensions мы укажем ocsp_ext, в этом разделе конфигурационного файла заданы расширения, относящиеся к подписанию ответов OCSP. Я сократил срок действия нового сертификата до 365 дней (вместо установленных по умолчанию 3650 дней). Поскольку в подобных сертификатах OCSP не содержится информация об отзыве, они не могут быть отозваны. По этой причине целесообразно установить срок действия такого сертификата настолько коротким, насколько это возможно. Хорошим выбором будет 30 дней, при условии, что вы готовы создавать новые сертификаты с такой частотой:

$ openssl ca \
    -config root-ca.conf \
    -in root-ocsp.csr \
    -out root-ocsp.crt \
    -extensions ocsp_ext \
    -days 30

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

$ openssl ocsp \
    -port 9080
    -index db/index \
    -rsigner root-ocsp.crt \
    -rkey private/root-ocsp.key \
    -CA root-ca.crt \
    -text

Работу ответчика OCSP можно протестировать следующей командой:

$ openssl ocsp \
    -issuer root-ca.crt \
    -CAfile root-ca.crt \
    -cert root-ocsp.crt \
    -url http://127.0.0.1:9080

В выводе команды verify OK означает, что проверка подписей прошла успешно, а good — что сертификат не был отозван.

Response verify OK
root-ocsp.crt: good
        This Update: Jul  9 18:45:34 2014 GMT

Создание подчинённого УЦ

Процесс создания подчиненного УЦ во многом аналогичен тому, который мы выполняли для корневого УЦ. В этом разделе мы главным образом будем останавливаться на их различиях, всё остальное можно найти в предыдущем разделе.

Конфигурация подчинённого УЦ

Чтобы составить конфигурационный файл для подчинённого УЦ (sub-ca.conf), возьмём за основу файл, который мы использовали для корневого УЦ, и внесём в него перечисленные ниже изменения. Мы изменим имя на sub-ca и будем использовать другое отличительное имя. Ответчик OCSP будет принимать запросы на другом порту, но лишь потому, что команда ocsp не умеет работать с виртуальными хостами. Если для организации ответчика OCSP используется полноценный веб-сервер, можно вообще обойтись без специализированных портов. По умолчанию срок действия новых сертификатов составит 365 дней, свежий CRL мы будем генерировать каждые 30 дней.

Изменение параметра copy_extensions на значение copy означает, что указанные в CSR расширения будут скопированы в сертификат, но только в том случае, если аналогичные расширения не были заданы в нашей конфигурации. При такой настройке те, кто готовят CSR, могут поместить в него необходимые альтернативные имена, и информация о них будет собрана и помещена в сертификат. Эта функция может быть потенциально опасна (вы позволяете стороннему лицу иметь ограниченный прямой контроль над тем, что попадёт в сертификат), но в небольших окружениях это вполне приемлемо:

[default]
name                    = sub-ca
ocsp_url                = http://ocsp.$name.$domain_suffix:9081

[ca_dn]
countryName             = "GB"
organizationName        = "Example"
commonName              = "Sub CA"

[ca_default]
default_days            = 365
default_crl_days        = 30
copy_extensions         = copy

В конце конфигурационного файла добавим два новых профиля: для клиента и для сервера. Различия между ними будут только в расширениях keyUsage и extendedKeyUsage. Обратите внимание, что мы указали расширение basicConstraints, но задали его в false. Это сделано потому, что мы копируем расширения из CSR. Если бы мы пропустили данное расширение, то вполне могли бы получить его из CSR с тем значением, которое указано там:

[server_ext]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical,CA:false
crlDistributionPoints   = @crl_info
extendedKeyUsage        = clientAuth,serverAuth
keyUsage                = critical,digitalSignature,keyEncipherment
subjectKeyIdentifier    = hash

[client_ext]
authorityInfoAccess     = @issuer_info
authorityKeyIdentifier  = keyid:always
basicConstraints        = critical,CA:false
crlDistributionPoints   = @crl_info
extendedKeyUsage        = clientAuth
keyUsage                = critical,digitalSignature
subjectKeyIdentifier    = hash

После того, как вы доведёте до ума конфигурационный файл, создайте структуру каталогов, следуя тем же инструкциям, что и для корневого УЦ, только не забудьте указать другое имя директории, например, sub-ca.

Генерация подчинённого УЦ

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

$ openssl req -new \
    -config sub-ca.conf \
    -out sub-ca.csr \
    -keyout private/sub-ca.key

На втором этапе мы воспользуемся корневым УЦ для выпуска сертификата. Параметр -extensions указывает на раздел конфигурационного файла sub_ca_ext, в котором активируются расширения, соответствующие подчинённому УЦ.

$ openssl ca \
    -config root-ca.conf \
    -in sub-ca.csr \
    -out sub-ca.crt \
    -extensions sub_ca_ext

Операции, выполняемые в рамках подчинённого УЦ

Для выпуска сертификата сервера при обработке CSR в параметре -extensions указывается server_ext:

$ openssl ca \
    -config sub-ca.conf \
    -in server.csr \
    -out server.crt \
    -extensions server_ext

Для выпуска сертификата клиента при обработке CSR в параметре -extensions указывается client_ext:

$ openssl ca \
    -config sub-ca.conf \
    -in client.csr \
    -out client.crt \
    -extensions client_ext

Примечание: При поступлении нового запроса на подписание сертификата, вся содержащаяся в нём информация должна быть представлена вам для проверки перед выполнением операции выпуска сертификата. Всегда следует проверить информацию и убедиться в том, что всё в порядке, но особенно в том случае, если CSR был подготовлен сторонним лицом. Обращайте особое внимание на отличительное имя сертификата, а также расширения basicConstraints и subjectAlternativeName.

Генерация CRL и отзыв сертификата осуществляются также, как и для корневого УЦ. Отличается лишь порт ответчика OCSP, для подчинённого УЦ это будет 9081. Рекомендуется, чтобы ответчик использовал свой собственный сертификат, что позволит избежать хранения подчинённого УЦ на общедоступном сервере.

Примечания

[1] Буквы “eay” в названии SSLeay представляют собой инициалы разработчика Eric A. Young.

[2] BoringSSL (Chromium, проверено 30 июня 2015 г.)

[3] Win32 OpenSSL (Shining Light Productions, проверено 3 июля 2014 г.)

[4] Бинарники и модули Apache 2.4 VC14 (Apache Lounge, проверено 15 июля 2015 г.)

[5] Пакет исходных кодов “openssl” в Precise (Ubuntu, проверено 3 июля 2014 г.)

[6] Компиляция с усиленной защитой в Ubuntu и Debian (Kees Cook, 3 февраля 2014 г.)

[7] Существуют организации, в которых придерживаются очень строгих требований обеспечения безопасности, в частности, защита закрытых ключей в них должна быть обеспечена любой ценой. Решением для таких организаций может стать инвестирование в Аппаратные модули безопасности (Hardware Security Module, HSM) — продукты, специально разработанные для того, чтобы сделать извлечение ключей невозможным даже при физическом доступе к серверу. Для достижения этого HSM не только генерируют и хранят ключи, но также выполняют все необходимые операции (например, генерацию электронной подписи). Обычно HSM очень дороги.

[8] RFC 2985: PKCS #9: Избранные объектные классы и типы атрибутов, версия 2.0 (M. Nystrom и B. Kaliski, ноябрь 2000 г.)

[9] Криптография/Атака "встреча посередине" (Wikibooks, проверено 31 марта 2014 г.)

[10] Публикация "Attacking Triple Encryption" ("Техники атак на 3DES") (Stefan Lucks, 1998 г.)

[11] Публикация "On the Security of RC4 in TLS and WPA" ("О безопасности RC4 в TLS и WPA") (AlFardan и другие, 13 марта 2013 г.)

[12] RFC 7905: Наборы алгоритмов шифрования для TLS ChaCha20-Poly1305 (Langley и другие, июнь 2016 г.)

[13] Шаблоны конфигурации УЦ для OpenSSL (GitHub-репозиторий книги "Bulletproof SSL and TLS", проверено 31 марта 2017 г.)

[14] "Базовые требования" (The CA/Browser Forum, проверено 9 июля 2014 г.)