О сертификатах

2021-09-19

Попросили рассказать о сертификатах. Рассказываю.

Для начала, вспомним про шифрование.

В симметричном шифровании всё просто. Если Алиса хочет отправить зашифрованное сообщение Бобу, она берёт незашифрованное сообщение, шифрует его ключом шифрования и передаёт Бобу. Боб получает зашифрованное сообщение, расшифровывает его ключом шифрования, и читает.

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

В ассиметричном шифровании у Алисы и Боба есть по паре ключей. Публичный и приватный. Свои приватные ключи Алиса и Боб бережно хранят у себя и никому не показывают. Своими публичными ключами Алиса и Боб обмениваются открыто. И даже могут переслать их Еве и Мэллори. На то они и публичные.

ключи

Если Алиса хочет отправить зашифрованное сообщение Бобу, она берёт незашифрованное сообщение и шифрует его публичным ключом Боба. После этого расшифровать сообщение можно только приватным ключом Боба. А этот ключ, по определению, есть только у Боба. Таким образом, зашифрованное сообщение можно переслать Бобу как угодно. Пусть Ева и Мэллори слушают. Расшифровать и прочитать сможет только Боб.

шифрование

Как Боб узнает, что сообщение написала именно Алиса? Ведь публичный ключ Боба знает и Мэллори, и она тоже может послать Бобу зашифрованное сообщение. Для этого используются подписи.

Алиса берёт сообщение, считает от него дайджест. Это те самые функции хеширования, типа SHA256. Затем Алиса шифрует этот дайджест своим приватным ключом. Этот зашифрованный дайджест и называется подписью. И передаёт Бобу не только зашифрованное (публичным ключом Боба) сообщение, но и подпись (зашифрованную приватным ключом Алисы).

подпись

Боб, получив сообщение с подписью, расшифровывает подпись публичным ключом Алисы. Получает некий дайджест. Если эта подпись была сформирована не Алисой, а Боб попытался расшифровать публичным ключом Алисы, то получится мусор. Но мусор — это тоже дайджест, просто неизвестного текста. Далее Боб сам считает дайджест полученного сообщения. Если расшифрованный из подписи дайджест совпадает с подсчитанным дайджестом, то, значит, это сообщение действительно подписала Алиса. И, между делом, это сообщение не было повреждено или переделано какой-нибудь Мэллори при передаче.

проверка подписи

Остаётся лишь проблема доверия. Откуда Алиса знает, что публичный ключ, который, как она считает, принадлежит Бобу, действительно принадлежит Бобу? То есть, что у Боба действительно есть парный приватный ключ. Вдруг это Мэллори передала свой публичный ключ Алисе, но сказала, что это ключ Боба?

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

В ssh просто запоминается отпечаток ключа сервера, и, если он изменился, клиент подымет тревогу. С другой стороны, при авторизации по ключу, сервер доверяет всем клиентам, чьи публичные ключи явно прописаны у него в файлике authorized_keys. Подразумевается, что доступ к этому файлу имеют только ранее авторизованные пользователи.

В PGP каждый пользователь самостоятельно определяет степень доверия публичным ключам других пользователей, которые хранятся в его связке ключей. Чтобы повысить эту степень доверия, проводят криптовечеринки. На этих вечеринках пользователи предъявляют друг другу отпечатки своих публичных ключей и паспорта. Таким образом каждый может убедиться, что ключи соответствуют реальным людям с настоящими документами.

Допустим, на криптовечеринке Трент увидел отпечаток публичного ключа Боба (буквально отпечатанный на бумажке), а также познакомился с самим Бобом и заглянул в его паспорт, чтобы убедиться, что этот чувак действительно Боб. Потом Трент заглянул в свою связку ключей, нашёл публичный ключ Боба (или скачал его из Интернетов), убедился, что отпечаток этого ключа совпадает с отпечатком, который передал ему лично Боб. Теперь Трент подписывает ключ Боба своим приватным ключом. И публикует (в Интернеты) эту свою подпись. Теперь публичный ключ Боба подписан Трентом.

Алиса не была на этой криптовечеринке и не встречала Боба лично. Но хочет отправить ему зашифрованное сообщение. У неё есть публичный ключ Боба, и она видит, что Трент подписал этот ключ. Зато Алиса была на другой криптовечеринке, и встречала Трента, и даже подписала его публичный ключ. Алиса доверяет Тренту. А Трент доверяет Бобу. Образуется цепочка доверия. Алиса, конечно, доверяет Бобу меньше, чем Тренту, потому что не встречалась с Бобом лично. Но, вполне вероятно, этого достаточно, чтобы отправить первое сообщение.

Аналогичная цепочка доверия формируется и, например, в PGP ключах, которыми подписываются пакеты Debian. Все публикуемые пакеты Debian подписываются PGP ключами. Чтобы стать мейнтейнером какого-нибудь пакета и публиковать собственные подписанные пакеты, нужно лично встретиться с другим мейнтейнером Debian, чтобы он подписал ваш публичный ключ.

Ещё раз. Подписывать можно не только произвольные сообщения или пакеты ПО, но и другие публичные ключи. Ключи же тоже данные.

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

сертификат

В мире сертификатов доверие тоже устанавливается цепочкой подписей. Сертификат конкретного сайта подписан ключом (и сертификатом) некоего удостоверяющего центра (CA — Certificate Authority), который «выдал» этот сертификат. Сертификат удостоверяющего центра тоже может быть подписан другим удостоверяющим центром. А тот другим центром. И так далее, пока мы не дойдём до так называемого корневого сертификата.

цепочка доверия

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

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

Посмотрим на сертификат Википедии:

$ openssl s_client -showcerts -connect ru.wikipedia.org:443 
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
verify return:1
depth=0 C = US, ST = California, L = San Francisco, O = "Wikimedia Foundation, Inc.", CN = *.wikipedia.org
verify return:1
---
Certificate chain
 0 s:C = US, ST = California, L = San Francisco, O = "Wikimedia Foundation, Inc.", CN = *.wikipedia.org
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
-----BEGIN CERTIFICATE-----
MIIIRzCCBy+gAwIBAgIQD+Jq/rlFfSfP2Z7uT19HXDANBgkqhkiG9w0BAQsFADBw
...
of8WgO+5LveizWU=
-----END CERTIFICATE-----
 1 s:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
   i:C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
-----BEGIN CERTIFICATE-----
MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs
...
cPUeybQ=
-----END CERTIFICATE-----
---
Server certificate
subject=C = US, ST = California, L = San Francisco, O = "Wikimedia Foundation, Inc.", CN = *.wikipedia.org
issuer=C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
---
...

Это wildcard сертификат на *.wikipedia.org. CN, Common Name, в сертификатах для веб сайтов содержит доменное имя. O, Organization, — Wikimedia Foundation, Inc. L, ST, C, Location, State, Country — Сан-Франциско, Калифорния, США.

Сертификат подписан неким DigiCert SHA2 High Assurance Server CA. Это промежуточный удостоверяющий центр. А корневой сертификат — это DigiCert High Assurance EV Root CA.

Почему OpenSSL клиент доверяет этому DigiCert? Ну давайте посмотрим, какие доверенные корневые сертификаты установлены у нас в системе:

$ ls -1 /etc/ssl/certs/Digi* 
/etc/ssl/certs/DigiCert_Assured_ID_Root_CA.pem
/etc/ssl/certs/DigiCert_Assured_ID_Root_G2.pem
/etc/ssl/certs/DigiCert_Assured_ID_Root_G3.pem
/etc/ssl/certs/DigiCert_Global_Root_CA.pem
/etc/ssl/certs/DigiCert_Global_Root_G2.pem
/etc/ssl/certs/DigiCert_Global_Root_G3.pem
/etc/ssl/certs/DigiCert_High_Assurance_EV_Root_CA.pem
/etc/ssl/certs/DigiCert_Trusted_Root_G4.pem

$ openssl x509 -in /etc/ssl/certs/DigiCert_High_Assurance_EV_Root_CA.pem -text 
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            02:ac:5c:26:6a:0b:40:9b:8f:0b:79:f2:ae:46:25:77
        Signature Algorithm: sha1WithRSAEncryption
        Issuer: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
        Validity
            Not Before: Nov 10 00:00:00 2006 GMT
            Not After : Nov 10 00:00:00 2031 GMT
        Subject: C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
...

Ну да, оно. Обратите внимание, что сертификат этот действителен в течение 25 лет. Типичный срок для корневых сертификатов. И подписан сам собой, Issuer равен Subject.

Стандарт на сертификаты — это X.509. В нём данные сертификата закодированы по стандарту ASN.1, который в настоящее время включён в X.690. X.690 определяет правила бинарного представления данных: BER, CER и DER (Distinguished Encoding Rules). А DER, представленный в base64 и записанный в текстовый файл, представляет часть стандарта PEM и соответствующие .pem файлы. Именно они чаще всего встречаются в наших юниксах, когда нужно передать ключи или сертификаты. Именно PEM вы видите от -----BEGIN CERTIFICATE----- до -----END CERTIFICATE----- в примере с Википедией выше.

Любопытно, что рядышком стоит стандарт X.500 на службу каталогов. Именно поэтому в LDAP или Active Directory имена сущностей записываются так же как в сертификатах.

Допустим, у нас есть сайт. Как получить для него сертификат?

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

$ openssl req -x509         `# запрос на подписывание сертификата X.509 Certificate Signing Request (CSR)` \
    -newkey rsa:4096        `# создаём новый RSA ключ длиной 4096 бит` \
    -subj '/CN=localhost'   `# сертификат на localhost` \
    -days 365               `# сертификат действителен 365 дней` \
    -keyout key.pem         `# приватный ключ записываем в файл key.pem` \
    -nodes                  `# не шифруем файл ключа паролем` \
    -out cert.pem           `# сертификат записываем в файл cert.pem`
Generating a RSA private key
.........++++
writing new private key to 'key.pem'
-----

Получился действительно сертификат.

$ openssl x509 -in cert.pem -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            64:5a:92:ff:8c:92:bc:6e:50:f6:02:8d:94:13:a1:ef:82:96:af:2a
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN = localhost
        Validity
            Not Before: Sep 11 11:22:51 2021 GMT
            Not After : Sep 11 11:22:51 2022 GMT
        Subject: CN = localhost
...

А в файле key.pem теперь хранится приватный ключ. Можно посмотреть на все эти коэффициенты и экспоненты этих самых простых чисел в RSA:

$ openssl rsa -in key.pem -text 
RSA Private-Key: (4096 bit, 2 primes)
modulus:
...

На самом деле в файле key.pem хранится пара ключей. И приватный, и публичный. Можно извлечь публичный ключ из этого файла: openssl rsa -in key.pem -pubout. Впрочем, публичный ключ без сертификата мало где нужен.

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

Создаём приватный ключ:

$ openssl genrsa -out example.key 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.............................++++
...

Теперь нужно сформировать запрос на подпись (поэтому req выше), CSR (Certificate Signing Request). Это публичный ключ, плюс метаданные, но ещё без подписи.

$ openssl req -new -key example.key -out example.csr
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]:RU
State or Province Name (full name) [Some-State]:Omsk
Locality Name (eg, city) []:Omsk
Organization Name (eg, company) [Internet Widgits Pty Ltd]:test
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:

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

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

Получился действительно почти сертификат, но без подписи:

$ openssl req -in example.csr -text 
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C = RU, ST = Omsk, L = Omsk, O = test, CN = example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (4096 bit)
                Modulus:
...

Далее вы должны этот .csr файл передать какому-нибудь CA, который подпишет ваш сертификат. Этот CA, конечно же, не будет этого делать бесплатно. Более того, выдаст сертификат на год (время действия сертификата определяется в момент его подписывания), а через год потребует ещё денег.

Но самое главное, этот CA проверит, что вы — это вы. И что ваш сайт, чей домен вы указали в CN в subject запроса на сертификат, — действительно ваш сайт. Они могут попросить вас создать в корне сайта какой-нибудь пустой файлик со случайным именем. Или добавить какую-нибудь TXT запись в домене вашего сайта. А потом проверят, появился ли такой файлик или DNS запись в домене, на который запрашивают сертификат.

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

Допустим, все проверки вы проходите. Тогда CA подписывает ваш запрос на сертификат примерно так:

$ openssl x509 -req -in example.csr -days 365 \
    -CA ca.crt -CAkey ca.key -set_serial 01 -out example.crt
Signature ok
subject=C = RU, ST = Omsk, L = Omsk, O = test, CN = example.com
Getting CA Private Key

Эти условные ca.crt и ca.key — это сертификат и ключ CA. Скорее всего не корневой сертификат, а некоторый промежуточный. Как мы видели у Википедии. И вряд ли настоящий CA использует openssl для подписи, там, скорее всего, специализированный софт. Нужно же вести тщательный учёт всех выданных сертификатов. Репутация же.

После подписи получается уже настоящий сертификат, который CA и передаёт вам. А вы уже скармливаете этот сертификат (и соответствующий ему приватный ключ) вашему веб серверу. Обратите внимание, что сам приватный ключ вы, как сгенерировали у себя, так никому, включая CA, не передавали.

Let's Encrypt делает всё то же самое. Просто он автоматизирует весь процесс. Локально на компьютере, где запущен certbot, генерируется приватный ключ и запрос на сертификат. В папочку на сервере помещается случайный файл согласно протоколу ACME. Затем запрос на сертификат отправляется на сервера Let's Encrypt. Сервера проверяют наличие случайного файла и подписывают сертификат. Certbot складывает обновлённый сертификат в правильное место и даже может перезапустить веб сервер.

Браузер, как правило, получает от сервера как сам сертификат данного сайта, так и цепочку промежуточных сертификатов. Корневой сертификат в эту цепочку обычно не включают, он и так уже есть в системе. А промежуточных сертификатов CA может наплодить сколько угодно. Поэтому их и нужно включать в ответ сервера, чтобы можно было пройти по всей цепочке подписей до доверенного корневого сертификата.

Браузер при установке HTTPS соединения получает с сервера цепочку сертификатов и проверяет её. Примерно так:

$ openssl verify -CAfile ca.crt example.crt 
example.crt: OK

Точнее, так, ибо используется встроенное в систему (или в браузер) хранилище доверенных сертификатов:

$ openssl verify example.crt 
C = RU, ST = Omsk, L = Omsk, O = test, CN = example.com
error 20 at 0 depth lookup: unable to get local issuer certificate
error example.crt: verification failed

Так как этот примерный сертификат не был подписан никаким настоящим CA, проверка не проходит.

Кроме того, браузер проверяет, а действительно ли CN сертификата совпадает с доменным именем сайта, куда он заходит. Wildcard сертификаты (как у Википедии *.wikipedia.org) могут соответствовать множеству доменных имён, но только на данном уровне иерархии этих имён (something.nested.wikipedia.org не пройдёт).

При работе с сертификатами, кроме X.509 и ASN.1, вам будут мозолить глаза стандарты семейства PKCS (Public Key Cryptography Standards). В них описаны алгоритмы, форматы файлов и контейнеров для криптографии.

PKCS#1 — это, собственно, описание RSA.

PKCS#3 — это описание протокола Диффи-Хеллмана — способа выработать общий ключ (для последующего симметричного шифрования) на основе пар ключей ассиметричного шифрования (никому не разглашая приватные ключи).

PKCS#7 — это контейнер для множества сертификатов, а также зашифрованных и подписанных сообщений.

PKCS#8 — это стандарт на хранение приватных ключей (пары ключей), опционально защищённых паролем. Приватные ключи key.pem или example.key выше — это, на самом деле, приватные ключи в формате PKCS#8, закодированные в DER и записанные в base64 согласно PEM.

PKCS#10 — это стандарт на Certificate Signing Request. Тот самый example.csr выше — это PKCS#10, закодированный в DER и записанный в base64 согласно PEM.

PKCS#12 — это контейнер для хранения приватных ключей и сертификатов, опционально защищенных паролем. То, что называется key store, и в жизни встречается в виде файлов .pfx и .p12. Используется для передачи и хранения ключей и цепочек сертификатов.

У Java есть свой собственный, ни с чем не совместимый, формат key store под названием JKS.

Напоследок, вот вам шикарная справочка по командам openssl.