Четыре года назад я написал статью Человеческим языком о цифровых сертификатах: ASN.1, X.509, PKI, в которой постарался максимально понятно рассказать о цифровых сертификатах с примерами их использования через консоль и openssl. С того времени многое изменилось: openssl версии 1 ушёл в массы, появились и стали активно использоваться его форки (libressl и boringssl), появилась (типа) поддержка гостовских алгоритмов. Плюс я получил неожиданно много обратной связи, чего совершенно не ожидал, и в итоге появился этот полностью переработанный и актуализированный текст.
*Целевая аудитория этой статьи — айтишники, поверхностно знакомые с понятием цифрового сертификата и сопутствующими понятиями и техническими стандартами (X.509, PKI, PKCS). Текст не является пересказом документации или сборником рецептов, воспринимайте его как короткий учебник, рассказывающий о базовых концептах криптографии и сложившейся вокруг стандарта X.509 инфраструктуры.
Чаще всего цифровой сертификат ассоциируется с браузером, веб-сайтом и шифрованием трафика в HTTPS-соединении. И обычно на самом факте существования сертификата у большинства айтишников понимание темы заканчивается. Многие статьи на эту тему с самого начала топят читателя в абстрактных и несущественных деталях (например, раз, два, три). Между тем, базовые концепции системы сертификатов очень простые и я о них расскажу.
В центре внимания у меня именно сертификат, так как именно вокруг него крутятся все остальные концепты.
Текст состоит из двух частей: достаточно подробная теоретическая основа (включая несколько базовых понятий из криптографии) и практическая часть с конкретными примерами на openssl. Все новые понятия в тексте вводятся последовательно, а примеры из практической части используют файлы, созданные в предшествующих разделах. Я старался пользоваться официальной русской терминологией, принятой в стандартах и литературе.
В жизненном цикле сертификата участвует несколько организаций или персон, исполняющих различные роли, в течение статьи я буду переключаться между ними, чтобы продемонстрировать аспекты каждой.
Для полноценной работы вам понадобится linux с терминалом и установленным openssl версии 1.0 или выше. С некоторым оговорками подойдёт macos с терминалом (в macos последних версий установлен форк openssl под названием libressl).
- Теория
- Что такое openssl
- Что такое и зачем нужны сертификаты
- Основы криптографии
- Использование криптографии в X.509-сертификатах
- Используемые криптоалгоритмы
- Концепт CSR и подписывания сертификата удостоверяющим центром
- Инфраструктура УЦ и сертификаты
- Цепочки доверия сертификатов на примере
- Отзыв сертификата
- Как работает активное проксирование TLS/SSL
- Практика
- Форматы данных
- Типы данных
- Генерация закрытого ключа
- Создание Certificate Signing Request
- Создание тестового удостоверяющего центра
- Создание сертификата из CSR
- Тестирование сертификатов
- Верификация цепочки сертификатов
- Использование сертификатов для цифровой подписи любых файлов
- Проверка соответствия сертификата и закрытого ключа
- Использование сертификатов для шифрования
- Несколько примеров необычных сертификатов
- Примечания
Теория¶
В теоретическом разделе я коротко расскажу о программе и библиотеке OpenSSL, затем будет небольшой обзор необходимых для дальнейшего понимания криптографических концептов, далее о том, как они используются для создания сертификатов и сопутствующих объектов. Завершу всё небольшим обзором инфраструктуры доверия к сертификатам на основе удостоверяющих центров.
Что такое openssl¶
OpenSSL — это опенсорсный набор библиотек и программ для работы с SSL/TLS и некоторыми распространёнными криптоалгоритмами. В этой статье мы будем работать только с программой openssl, она представляет собой коллекцию утилит (команд) для операций над крипто-объектами: ключами, сертификатами, зашифрованными данными.
OpenSSL — очень старая система, в ней огромное количество legacy-кода и legacy-интерфейсов. Различные команды из её состава принимают разные аргументы и имеют разную логику работы. Команд этих очень много и я не буду рассказывать о них, вместо этого я сфокусируюсь на конкретных задачах, в рамках которых буду давать примеры использования команд для её решения. Иногда путей для решения задачи будет несколько.
Что такое и зачем нужны сертификаты¶
Для начала определимся, что вообще означает слово сертификат. В бытовом понимании это специальный документ, удостоверяющий какой-нибудь факт, знание или компетенцию; причём важно, что документ выдан конкретной (сертифицирующей) организацией. У этого документа есть признаки, по которым можно проверить, что он действительный и не поддельный. Например, уникальный номер, печать организации, подпись ответственного лица, срок действия и так далее. Важно, что имеются чётко определённые процедуры верификации сертификата, например, сравнение подписи, печати, запрос в организацию по номеру и так далее.
В русском языке слово сертификат употребляется наравне со словами удостоверение, свидетельство; особенно это характерно для бюрократии, официальных текстов и государственных стандартов. Например, в российской Системе документов по аккредитации в документе СДА 06-2009 используется такое определение:
Удостоверение (сертификат) - документ, выданный органом по сертификации персонала, удостоверяющий компетентность специалиста в определенной области испытаний в соответствии с присвоенным уровнем квалификации.
Поскольку мы говорим на русском и многие из нас работают с русскоязычной нормативной лексикой, дальше я буду пользоваться в том числе и «официальными» терминами при необходимости сопровождая их оригинальными англоязычными.
Термин цифровой сертификат (digital certificate) означает, что первичная его форма — в виде байтов на диске, памяти компьютера, на носителе информации. Цифровой сертификат можно легко скопировать и полученная копия будет обладать всеми его свойствами. В основе цифровых сертификатов лежит криптография, именно с помощью криптоалгоритмов происходит работа с сертификатом на всех этапах его жизненного цикла: создание, использование, уничтожение. Без знания основ криптографии, хотя бы самых элементарных, невозможно полноценно понять суть и смысл цифровых сертификатов, поэтому я про них тоже расскажу.
В дальнейшем я под словом сертификат буду подразумевать исключительно цифровой сертификат.
Основы криптографии¶
Я не буду углубляться в детали, а лишь расскажу коротко и максимально доступно о ключевых криптографических концептах, лежащих в основе нашей темы. Хотя бы базовое их знание критично важно для полноценного понимания принципов работы цифровых сертификатов и сопутствующей инфраструктуры.
Википедия нам даёт такое определение:
Криптогра́фия (от др.-греч. κρυπτός «скрытый» + γράφω «пишу») — наука о методах обеспечения конфиденциальности (невозможности прочтения информации посторонним), целостности данных (невозможности незаметного изменения информации), аутентификации (проверки подлинности авторства или иных свойств объекта), шифрования (кодировка данных).
Все эти аспекты используются при работе с сертификатами. Начнём с шифрования, в упрощённой форме это процесс преобразования открытого текста в зашифрованный и наоборот, из зашифрованного в открытый — этот процесс называется дешифрованием, дешифровкой, расшифровкой. Шифрование и дешифрование работают на самом деле с произвольными наборами байтов, поэтому фразу открытый текст нужно воспринимать как открытый набор байтов.
В криптографии принято алгоритм алгоритм шифрования/дешифрования называть просто шифр (cipher). Считается, что шифр сам по себе не может быть секретным, его алгоритм должен быть общеизвестным, а секретность обеспечиваться ключом — специальным набором байтов, который определяет, как именно алгоритм будет зашифровывать/расшифровывать данные. Общеизвестность нужна для того, чтобы шифр могли исследовать специалисты, подтверждая или опровергая его надёжность.
Если для шифрования и дешифрования используется один и тот же ключ, то такой алгоритм называется симметричным. Если для шифрования и дешифрования используются разные ключи, то алгоритм называется асимметричным. У каждого из таких типов есть свои области применения.
Важным для нас вариантом асимметричного шифра является криптосистема с открытым ключом (public-key cryptography). В её основе лежат такие принципы:
- ключ состоит из двух компонентов: закрытого ключа / private key (предполагается, что он секретный и тщательно охраняется) и открытого ключа / public key (который может и должен распространяться свободно по любым каналам);
- открытый ключ вычисляется из закрытого простым способом, а вот обратная процедура (вычисление закрытого ключа из открытого) является чрезвычайно сложной, затратной и невыполнимой за разумное время на любом доступном оборудовании;
- существует открытые, общеизвестные и надёжные алгоритмы, которые используют закрытый и открытый ключ для криптографических операций, например, для шифрования/дешифрования.
Часто закрытый ключ также называют приватным или секретным, открытый ключ называют публичным, но я в тексте буду придерживаться терминов закрытый/открытый.
Первая схема использования пары закрытого и открытого ключей — шифрование: отправитель шифрует текст, затем отправляет получателю зашифрованный текст через открытый канал, а получатель их расшифровывает снова в открытый текст. Эта схема предназначена для защиты информации, вот что для неё требуется:
- предполагается, что у отправителя уже есть открытый ключ, а у получателя — закрытый (парный к открытому, естественно);
- отправитель при помощи открытого ключа и алгоритма (шифра) шифрует данные и отправляет результат через открытые каналы;
- получатель при помощи закрытого ключа расшифровывает данные и восстанавливает исходный текст.
Поскольку открытый ключ общеизвестен, любой человек может зашифровать при помощи него сообщение, а дальше подменить оригинальное. У получателя нет никакого способа автоматически проверить (то есть аутентифицировать), что полученное им сообщение не было изменено и пришло именно от нужного отправителя. И тут на помощь приходит вторая схема использования пары ключей — цифровая подпись / digital signature. Она используется для подтверждения авторства сообщения, то есть для аутентификации. Вот пример:
- у отправителя файла (и только у него!) есть доступ до закрытого ключа, а открытый ключ является публичным и доступен в том числе получателю;
- отправитель берёт файл и при помощи алгоритма подписи и закрытого ключа создаёт цифровую подпись — ещё один набор байтов в виде отдельного файла, который отправляется получателю вместе с оригинальным текстовым файлом по открытому каналу;
- получатель берёт текстовый файл и файл подписи и при помощи алгоритма проверки цифровой подписи и открытого ключа верифицирует, создана ли эта подпись закрытым ключом, парой которому является данный открытый ключ.
Алгоритм подписи чаще всего оперирует не с сообщением целиком, а с его дайджестом (digest), то есть «сжатым» при помощи алгоритма криптографического хеширования (например, SHA-1 или гостовского СТРИБОГ). И в целом схема выглядит так:
На практике байты цифровой подписи и байты собственно исходного (подписанного) сообщения укладываются в один файл-контейнер, но важно понимать, что в процессе верификации они разделяются. Цифровая подпись обычно достаточно короткая, порядка нескольких сотен байтов максимум.
Во многих статьях (например, здесь) можно встретить некорректное и устаревшее описание цифровой подписи, в нём она является зашифрованным с помощью закрытого ключа дайджестом, а соответственно верификация производится расшифровкой подписи с помощью открытого ключа и последующим сравнением результата с отдельно подсчитанным на стороне получателя дайджестом сообщения. Такой процесс возможен для RSA, почти во всех остальных криптосистемах шифрование собственно открытыми/закрытыми ключами принципиально не поддерживается, а поддерживается только создание цифровой подписи. Однако для организации защищённого канала этого достаточно и я ниже в разделе Используемые криптоалгоритмы подробно расскажу, как при помощи произвольного алгоритма цифровой подписи организовать безопасное шифрование и дешифрование данных. А в практическом разделе расскажу, как это сделать командами openssl.
──────────────────
В итоге, если у отправителя и получателя есть пара из собственного закрытого и открытого ключа другой стороны, они могут безопасно обмениваться сообщениями. То есть с шифрованием и аутентификацией.
Использование криптографии в X.509-сертификатах¶
Цифровой сертификат в общем случае — это набор байтов, состоящий из двух блоков: информационного (например, название веб-сайта, название организации и т.п.) и цифровой подписи для информационного блока. Цифровая подпись создаётся удостоверяющим центром (УЦ) / certification authority (CA) и таким образом удостоверяет аутентичность данных из информационного блока. Но каким образом удостоверяющий центр проводит проверки, что подписываемый сертификат содержит данные той персоны или организации, которая подаёт его для подписи? Каким образом клиенты проверяют достоверность цифровой подписи в сертификате? Обо всём этом я подробно расскажу. Но сначала начнём со стандартов.
Существует несколько бинарных форматов для представления сертификатов, однако в абсолютном большинстве случаев вам придётся иметь дело с X.509-сертификатами. Все остальные форматы нишевые и я про них здесь не буду рассказывать.
X.509 — это технический стандарт, определяющий формат для сертификата с открытым ключом, то есть для такого сертификата, в информационном блоке которого записан (помимо других данных) открытый ключ. Как правило вместе с открытым ключом указываются личные данные / identity персоны или организации, владеющей соответствующим закрытым ключом.
Существует также набор стандартов для криптографии с открытым ключом PKCS (Public Key Cryptography Standards). На данный момент в нём 15 стандартов, их принято обозначать как PKCS#1, PKCS#2 и т.д. С полным списком можно ознакомиться в википедии: https://en.wikipedia.org/wiki/PKCS, а я в тексте буду на нужные мне части ссылаться по мере необходимости.
Изначально стандарты семейства PKCS разрабатывались компанией RSA Security LLC в целях рекламы крипто-алгоритмов, патенты на которые были на руках у компании, поэтому в индустрии отношение к этому наборы было и остаётся настороженным.
В середине девяностых IETF и NIST сформировали рабочую группу Public-Key Infrastructure (X.509), которая позднее стала называться просто PKIX. В рамках рабочей группы были разработаны стандарты, детально описывающие, как нужно использовать X.509 на практике: RFC 3280 и его наследник RFC 5280.
Для описания структуры используемых данных чрезвычайно широко используется стандарт Abstract Syntax Notation One (ASN.1), фактически все типы данных в прикладной криптографии описаны через ASN.1-нотацию.
Используемые криптоалгоритмы¶
В инфраструктуре открытых ключей чаще всего используются RSA, DSA и ECC.
- RSA — криптосистема, названная по первым буквам имён её создателей: Rivest-Shamir-Adleman. В основе её теории лежит сложная задача факторизации (разделения на множители) произведения двух очень больших простых чисел. Выбирается два случайных очень больших простых числа p и q, их произведение n = p · q называется модулем / modulus и длина модуля в битах задаёт длину закрытого ключа. Чем больше длина, тем более надёжным считается ключ. Также выбирается сравнительно небольшое простое число e (чаще всего это 65537), называемое открытой экспонентой / public exponent. Дальше из модуля n вычисляется по специальному алгоритму (который я тут не буду объяснять) закрытая экспонента / private exponent. В итоге формируется открытый ключ в виде пары чисел (e, n) и закрытый ключ в виде пары (d, n). Этот алгоритм долгое время был самым распространённым и по умолчанию фигурировал во всех инструкциях и мануалах. Однако в середине девяностых был разработан алгоритм для квантовых компьютеров, выполняющий факторизацияю чисел. Хотя квантовых компьютеров ещё нет, но теперь RSA считается потенциально слабым, если длина закрытого ключа меньше 2048 бит. RSA содержит алгоритмы как для шифрования, так и для цифровой подписи.
- DSA — криптографический алгоритм, названием расшифровывается как Digital Signature Algorithm, то есть алгоритм цифровой подписи. Он основан на математической теории возведения в степень по модулю и задаче дискретного логарифмирования. В отличие от RSA, этот алгоритм может использоваться только для подписывания данных, но не для их шифрования. Исторически DSA использовался и продолжает использовать в операциях с государственными органами США, однако нас он не интересует, поэтому больше о нём не будем, просто имейте в виду, что он поддерживается при создании сертификатов тоже. DSA может использоваться только для цифровой подписи.
- ECC (или просто EC) — набор криптосистем на основе теории эллиптических кривых над конечным полем. Сейчас эти виды шифров используется всё активнее вместо RSA, поскольку они быстрее, размер ключа значительно меньше, потенциально устойчивее к взлому на квантовых компьютерах. Эллиптическая криптография построена на основе набора алгебраических операций на эллиптической кривой, которые, в свою очередь, строятся на основе операций в конечном поле, над которым задана кривая. В общем, это такая хардкорная высшая математика, знать которую совсем не обязательно для использования шифров. Крипто-алгоритмов на основе EC достаточно много, часть из них была перенесена из старых классических крипто-систем, часть придумана заново. Например, алгоритм цифровой подписи ГОСТ 34.10-2018 также использует эллиптические кривые для работы. Некоторые из ECC-алгоритмов могут использоваться для шифрования, а некоторые для цифровой подписи.
Из широко известных криптосистем только RSA позволяет собственными ключами шифровать и подписывать, все остальные непосредственно используются только для создания цифровой подписи. Однако можно пользоваться алгоритмом Diffie-Hellman для создания общего ключа для симметричного алгоритма (например, AES) и дальше уже им зашифровывать и расшифровывать данные.
Протокол Diffie-Hellman (Diffie–Hellman key exchange, дальше я буду его коротко называть DH) играет чрезвычайно большую роль в современной криптографии, поэтому я о нём расскажу подробно.
Суть DH в том, что две стороны могут безопасным образом создать одинаковый ключ через открытый канал связи. В википедии и других статьях принцип его работы обычно иллюстрируется через смешение цветов, но мне такое объяснение кажется не очень понятным, поэтому я его сильно упростил.
Итак, на разных сторонах открытого сетевого канала находятся Алиса и Боб которым нужно договориться о ключе (в моём примере это целое число) для симметричного шифра. Просто так передать его по сети нельзя, так как канал открытый и априори небезопасный.
- Алиса и Боб придумывают по секретному числу, например, Алиса выбирает
9
, а Боб —2
. - Алиса и Боб через открытый канал договариваются о каком-нибудь общем целом числе, скажем,
18
. Это число не является секретным, но при каждом сеансе DH оно должно быть новым. Самый простой способ договориться, когда сторона, начинающая коммуникацию, просто выбирает число и другая сторона его принимает. - Дальше каждая сторона складывает своё секретное число и общее число, у Алисы получается
9 + 18=27
, а у Боба2 + 18=20
. Полученный результат каждый отправляет другой стороне по открытому каналу. - Полученное число Алиса и Боб складывают со своим секретным числом: у Алисы получается
20 + 9 = 29
, а у Боба27 + 2 = 29
. - Вот это число
29
и является ключом, который каждая из сторон использует для шифрования/дешифрования.
Я выбрал операцию сложения ради простоты объяснения, в реальном протоколе используется сложно-обратимая операция. Если её обозначить через ⊕, то для произвольных значений X
и Y
вычисление Z = X ⊕ Y
выполняется легко; а зная Z
и X
, вычислить Y
исключительно сложно и затратно. В реальном DH используется функция возведения в степень в мультипликативной группе вычетов по простому модулю, её обратная операция — дискретный логарифм — считается крайне сложной и на данный момент не имеет эффективного решения.
──────────────────
Протокол DH, очевидно, уязвим для атак типа Man-in-the-middle, то есть третья сторона может незаметно вклиниться в обмен данными и полностью перехватывать и расшифровывать все данные. Поэтому в реальной жизни обмен блоками данных в DH сопровождается алгоритмами аутентификации, например, цифровой подписью.
Рассмотрим такую ситуацию: есть клиент и есть сервер, к которому подключается клиент через небезопасный и незащищённый канал. Также выбран некоторый криптоалгоритм цифровой подписи. У сервера есть закрытый ключ, а его открытая часть уже есть у клиента. Тогда аутентифицированный DH может работать, например, так:
- Клиент генерирует случайное секретное значение X общее публичное значение A.
- Клиент инициирует соединение к серверу и отправляет по нему A.
- Сервер получает A, генерирует своё секретное значение Y и вычисляет публичное значение B на основе публичного значения A и своего секретного Y.
- Сервер вычисляет общий секретный ключ K на основе публичного значения A и своего секретного значения Y.
- Сервер вычисляет цифровую подпись S от объединённого набора значений A и B и отправляет клиенту публичное значение B и цифровую подпись S.
- Клиент получает публичное значение B, вычисляет на основе B и своего секретного значения X общий секретный ключ K.
- Клиент проверяет цифровую подпись K объединённого набора значений A и только что полученного B (помним, что у клиента уже есть открытый ключ сервера).
- Если верификация подписи проходит успешно, вычисленный ключ K используется для шифрования и дешифрования данных между клиентом и сервером при помощи симметричного алгоритма.
В такой схеме клиент может быть уверен, что между ним и сервером нет третьей стороны, которая перехватывает данные, гарантией этому служит цифровая подпись. Такой ключ K, который создаётся на время сессии, называется эфемерным ключом (ephemeral key).
Ранее мы уже договорились, что контейнером для распространения открытого ключа является цифровой сертификат. И теперь остаётся вопрос: каким образом сертификат сервера оказывается у клиента перед установкой соединения.
Концепт CSR и подписывания сертификата удостоверяющим центром¶
Я буду использовать здесь и дальше слово заявитель / applicant для обозначения персоны или организации, которая хочет выпустить цифровой сертификат. Я специально не использую для этого контекста слово клиент, так как оно слишком общее и обычно применяется в контексте типа клиент/пользователь вебсайта.
Под личными данными заявителя я подразумеваю его имя, название организации, адрес, город и прочую информацию, которую в англоязычной терминологии принято называть identity.
Чтобы удостоверяющий центр создал сертификат с открытым ключом и личными данными заявителя, ему нужно убедиться как минимум в двух вещах:
- Заявитель является именно тем, за кого себя выдаёт, то есть предоставленные им личные данные точно его или его организации, уполномоченным представителем которой он является.
- У заявителя есть закрытый ключ для того открытого, который он предоставил удостоверяющему центру для подписи вместе с личными данными.
Первый пункт — это процедура, которая проводится административными, а не криптографическими методами, поэтому о ней здесь не будем рассказывать — это проверка паспорта, учредительных документов, звонки по телефонам, различные заверенные у нотариуса документы и так далее. А вот вторая отлично решается в автоматическом режиме криптографически — достаточно заявителю подписать собственным закрытым ключом блок с информационными данными (куда также записан собственно этот же открытый ключ!). В этом случае удостоверяющий центр берёт открытый ключ из информационного блока и верифицирует им цифровую подпись всего блока.
Вот этот вот информационный блок (состоящий из личных данных и открытого ключа) плюс цифровая подпись для него формируют запрос на подпись сертификата / certificate signing request (CSR). Для X.509 формат данных CSR определён в спецификации PKCS#10, он достаточно простой и по сути представляет собой линейный список информационных полей, поэтому я особо не буду углубляться в описание его формата.
В удостоверяющем центре после верификации CSR выделяют из всего блока личных данных нужные, дополняют их данными УЦ, после чего получившийся новый блок подписывают закрытым ключом УЦ и получается X.509-сертификат.
Все эти шаги я нарисовал на одной схеме:
TBS Certificate расшифровывается как to be signed certificate, то есть, данные для подписи, подписываемый сертификат.
Такая инфраструктура называется Public key infrastructure (Инфраструктура открытых ключей) или сокращённо PKI.
Инфраструктура УЦ и сертификаты¶
X.509-сертификат состоит из стандартного набора полей, часть из них обязательна, часть нет. Официального перевода этих названий на русский нет, поэтому я преимущественно буду пользоваться оригинальными именами с пояснениями при необходимости.
В каждом X.509-сертификате есть два обязательных поля: issuer и subject. Поле subject (то есть субъект, в философском смысле: носитель деятельности, осуществляющий активность) содержит личные данные заявителя и по сути является названием сертификата, его главным идентифицирующим признаком, именем. Я специально не использую термин идентификатор, поскольку в контексте сертификата такое поле уже есть, причём оно является необязательным. Содержимое берётся из одноимённого поля в CSR при создании сертификата и обычно представляет собой имя персоны, организации или домен веб-сайта.
В поле issuer хранится имя сертификата удостоворяющего центра. Постоянно помним, что открытые ключи у нас распространяются исключительно внутри сертификата. Поэтому в поле issuer находится содержимое поля subject сертификата УЦ, внутри которого находится открытый ключ, закрытая часть которого используется для подписи сертификата заявителя.
Поля issuer и subject являются структурированными, то есть это не просто строчки текста, а оформленные в жёстко заданную структуру данные типа distinguished name. Об этом подробнее я расскажу в практическом разделе.
Таким образом любой сертификат A в подобной инфраструктуре подписан закрытым ключом, открытая часть которого записана в каком-то другом сертификате B. Для простоты обычно в таком случае говорят, что сертификат A подписан сертификатом B. В свою очередь сертификат B подписан сертификатом C и так далее. Образуется цепочка зависимостей, которая завершается сертификатом Z специального вида, в котором поля issuer и subject совпадают. Это означает, что сертификат Z подписан закрытым ключом, открытая часть которого записана в этом же сертификате. Он так и называется — самоподписанный сертификат (self-signed certificate).
──────────────────
Считается, что вы как клиент доверяете (trust) сертификату A, если вы доверяете записанным в нём данным. Например, если вы получили сертификат организации через надёжный канал от надёжного представителя организации и записали на надёжном диске. Используя доверенный сертификат, а точнее, открытый ключ из него, вы можете организовать криптографически защищённый канал связи, например, до веб-сайта. Однако вы физически не сможете для каждого веб-сайта в таком режиме содержать «реестр» доверенных сертификатов и отслеживать его актуальность (сертификаты у сайтов часто меняются, например).
Описанная выше схема зависимостей между сертификатами лежит в основе системы доверия на базе удостоверяющих центров. Подразумевается, что если вы доверяете сертификату B, то вы также доверяете сертификату A, который им подписан. Так как организация-владелец сертификата B может им подписать множество других сертификатов, вы автоматически доверяете всем им. Следуя по цепочке доверия вы в итоге спускаетесь до самоподписанных сертификатов. Если вы доверяете самоподписанному сертификату Z, то вы автоматически доверяете всем остальным, которые имеют Z в цепочке доверия.
Естественно, бесконтрольное подписывание сертификатов ломает схему автоматического доверия, поэтому в сертификате есть специальное поле, которое разрешает или запрещает использовать этот сертификат при построении цепочки доверия. Если в вашем сертификате оно запрещает, то вы по-прежнему сможете подписать им другой сертификат, однако алгоритм верификации сразу такую цепочку «зарежет».
В итоге, только удостоверяющие центры (certification authority) обладают сертификатами, которые можно использовать для подписывания других сертификатов, чтобы они могли включаться в цепочку доверия. Их принято называть CA-сертификатами (CA certificate). А финальный самоподписанный сертификат в цепочке доверия называется сертификатом корневого удостоверяющего центра или просто корневым сертификатом (root certificate).
В итоге получается, что корневой сертификат лежит в основе огромного количества цепочек доверия, которые автоматически верифицируюся, если корневой сертификат находится в статусе доверенного. В любой современной операционной системе есть системное хранилище доверенных корневых сертификатов, эти сертификаты (и соответственно хранилище) меняются чрезвычайно редко и как правило это делается при обновлении операционной системы.
──────────────────
Также в сертификате есть множество других полей, которые ограничивают его применение. Например, диапазон дат, внутри которых сертификат можно считать доверенным. Практически все системы помечают сертификат недоверенным, если текущая дата в системе лежит вне указанного в сертификате диапазона. Диапазон действия корневых сертификатов обычно очень большой, порядка 10-20 лет. Этот диапазон дат указывается в полях notBefore и notAfter.
Здесь нужно отметить, что интерпретация «срока годности» сертификата целиком лежит на стороне программного обеспечения, которое сертификат использует. Нет никаких объективных причин, по которым сертификат перестаёт быть доверенным после достижения некоторой даты. Однако ограниченный срок жизни гарантирует стабильный источник дохода удостоверяющим центрам, которые выписывают сертификаты за весьма большие деньги.
──────────────────
Когда удостоверяющий центр выписывает сертификат, он записывает в поле serialNumber числовое значение, которое должно быть разным для каждого сертификата, заверенного этим конкретным сертификатом УЦ (X.509, п. 4.1.2.2). Туда можно записывать порядковый номер, можно текущую дату-время, можно случайное число, главное требование — это число должно быть уникальным для каждого выписанного сертификата.
──────────────────
X.509 — стандарт очень гибкий и позволяет добавлять произвольные дополнительные поля помимо базовых, это делается через расширения (extensions), я о них подробнее расскажу в практической части.
Цепочки доверия сертификатов на примере¶
Понятнее всего будет объяснить цепочки доверия на примере веб-сайтов и браузера.
У каждого работающего через SSL/TLS сайта имеется X.509-сертификат, подтверждающий его identity. Когда браузер устанавливает первое защищённое соединение с веб-сервером, они обмениваются информацией об используемых криптоалгоритмах, в рамках этого обмена веб-сервер отдаёт браузеру набор сертификатов, в котором есть обязательно сертификат собственно сайта, а остальные — промежуточные сертификаты для построения цепочки доверия.
Браузер сначала определяет, какой из переданных сертификатов является сайтовым, после чего пытается построить цепочку доверия из остальных полученных сертификатов и одного из доверенных, которые доступны браузеру. В цепочке каждый сертификат, начиная с сайтового, подписан следующим перед ним сертификатом. Если итоговая цепочка завершается доверенным, браузер начинает доверять сайтовому сертификату и продолжает безопасное подключение с его использованием.
Здесь важный момент: браузер получает от сайта несколько сертификатов, чтобы это произошло, владелец сервера при его конфигурации добавляет не только сертификат сайта, но и промежуточные сертификаты, которые он получил от удостоверяющего центра.
Отзыв сертификата¶
Удостоверяющий центр может отозвать (revoke) любой выписанный им сертификат. Обычно это происходит при компрометации сертификата, когда связанный с ним закрытый ключ воруют злоумышленники. Ну или владелец сертификата по каким-то своим причинам решил сертификат принудительно лишить статуса доверенного у всех пользователей.
Изначально каждый удостоверяющий центр поддерживал собственный ресурс (веб-сайт) с регулярно обновляемым списком отозванных сертификатов, они так и назывались — списки отозванных сертификатов (certificate revocation list, сокращённо CLR). Программы должны скачивать эти CLR, парсить их и затем принудительно помечать указанные там сертификаты как недоверенные. Такая схема очень неэффективна и ресурсозатратна, поэтому она постепенно заменяется на новую — Online Certificate Status Protocol или OCSP.
OCSP позволяет практически в режиме реального времени проверять, отозван ли сертификат удостоверяющим центром, и некоторые браузеры его активно используют.
──────────────────
Помимо утечки закрытого ключа удостоверяющего центра, причиной отзыва может стать халатность самого УЦ, который выписывал сертификаты без должной проверки направо и налево. Либо использовал небезопасные или уязвимые криптоалгоритмы.
Как работает активное проксирование TLS/SSL¶
Активное проксирование означает, что провайдер вклинивается в потом сетевого трафика и пропускает его через специальные программы, иногда модифицируя. В случае зашифрованного трафика задача провайдера усложняется, так как для получения доступа к содержимому он должен выступить в роли клиента, расшифровать данные, а затем снова их зашифровать, отдавая настоящему клиенту.
Естественно, зашифровать тем же закрытым ключом он не может, так как он есть только у оригинального владельца сайта-источника TLS-трафика, поэтому провайдер шифрует отдаваемый настоящему клиенту поток данных собственным закрытым ключом. При этом у клиента браузер начнёт выдавать ошибки, поскольку в его базе нет доверенного сертификата, закрытым ключом которого провайдер шифрует данные. Чтобы ошибок не было, провайдер может заставить клиентов установить в систему свой корневой сертификат и сделать его доверенным.
Именно такая схема обычно используется в компаниях, которые хотят отслеживать весь свой трафик — они ставят на каждую доступную машину сертификат собственного удостоверяющего центра, которым они подменяют оригинальные сертификаты всех сайтов.
И вот тут начинается самое интересное — некоторые программы специально игнорируют системное хранилище сертификатов и вместо него используют собственное, в котором может быть вообще всего лишь один корневой сертификат. Как правило такое делают мобильные приложения банков. Если вдруг в трафике появится сайт с незнакомым сертификатом, приложение автоматически перестанет работать, так как не сможет вообще установить соединение.
Практика¶
Я начну с обзора форматов данных, на которых всё построено, и дальше последовательно пройду по всем этапам жизненного цикла сертификата: создание закрытого ключа, создание CSR, создание сертификата на основе этого CSR. Мы будем очень интенсивно использовать консольную программу openssl из состава одноимённой библиотеки. Версия openssl должна быть 1 или лучше 1.1. Бо́льшая часть команд будет работать и в форках, например, libressl в макоси или boringssl в андроиде. Удобнее всего пользоваться терминалом в linux, macos, либо в новом режиме windows.
Форматы данных¶
Первичным форматом представления сертификатов (а часто и других объектов в openssl) в бинарном виде является DER (Distinguished Encoding Rules). DER в свою очередь является способом представления в бинарной форме структурированных данных, описанных на специальном языке ASN.1, который традиционно используется в телекоме и сетях. ASN.1 не просто язык описания структурированных данных, для него разработаны инструменты трансляции ASN.1-нотации в код на множестве языков программирования, который кодирует и декодирует бинарные объекты в типизированные объекты языка, например, в структуры или классы. При этом важным свойством DER-объектов является интегрированность информации о структуре прямо в данных, то есть вы можете взять DER-файл и программно разобрать его на составные части даже без формального описания в ASN.1. Естественно, вы не будете знать смысл отдельных полей и какие именно данные там закодированы, но вы как минимум увидите структуру и стандартные типы: кортежи, целые числа, строки (в том числе юникодные).
Если вам интересно, можете прочитать подробное описание стандарта и правил кодирования в статье википидии про стандарт X.680 (именно он описывает ASN.1) и X.690 (он описывает кодирование бинарных объектов для ASN.1), также эту тему я затронул в своей статье про кодирование данных на смарт-картах. В этой статье я не буду рассказывать о синтаксисе и других деталях ASN.1, однако будут приводить упрощённые описания на нём, так как они достаточно простые и лёгкие для базового понимания.
Структуру и данные («дамп») DER-объекта можно посмотреть командой openssl asn1parse
. Примеры её использования будут дальше в этой статье, когда я буду рассказывать о внутреннем устройстве различных бинарных крипто-объектов и буду приводить «дампы» их структуры.
──────────────────
Второй распространённый формат данных — PEM, это своего рода контейнер, позволяющий записать бинарные данные в ограниченном символьном наборе Base64, пригодном для передачи через электронную почту, чаты или даже бумагу. Один блок данных в PEM-формате выглядит так:
-----BEGIN RSA PRIVATE KEY----- MIIBOgIBAAJBAKhGNt7cXXgzTk9NaAjdJy5Lpfq3mIqws4Ev7zf1Idh043hcAFbB /uQr/BISsrAU170bLmAXE14s3edWkQNIWaECAwEAAQJAFcFmLK/+4aB4emY+kg7N lv2uythbv2qS+pvQ6MIniw10AZ+Ypa78x67DaZMeIRFPB9EkKs2AyYzzQmd/DBId MQIhANOlzUg24pOCT3bsB3ZcDXs52iyBYa+vk99w0S7ybF7NAiEAy4mSyjbJI1Uv e9zhdYwXjYutp+z6tjlYtIUAMBNNPiUCIE++mANOkr5Lig9fzUv+USIN4TOFqD3e 5NN6mYab1tM9AiALHkzCdxOttm2NmpdGUIzI0qR909guNBvAYLON7L//cQIhALr7 12CHrerflaB6KknHPDtA+1rEKdut36RQ/5WmqVdX -----END RSA PRIVATE KEY-----
Блок начинается с символов -----BEGIN
, дальше идёт метка, обозначающая тип хранимых данных, потом -----
, затем кусок закодированных в Base64 данных и дальше -----END
, снова та же метка и в конце опять -----
.
Внутри одного файла может быть несколько PEM-блоков, причём вокруг них может быть произвольный текст, который обычно программами игнорируется. У PEM-файлов часто используется расширение .pem
, либо расширения, обозначающие тип хранимых там объектов, к примеру, .cer
или .crt
для сертификатов или .key
для закрытых ключей.
Если раскодировать через base64 текст внутри PEM-блока, обычно получим корректный DER-объект. Таким образом, для программы метка в PEM-блоке служит подсказкой, какой тип данных находится внутри.
Практически всегда мы будем работать именно с PEM, более того, openssl по умолчанию использует его во всех своих командах для ввода и вывода.
Типы данных¶
Object identifier (OID)¶
В описанных через ASN.1 данных для идентификации различных сущностей используется OID (Object indentifier) — это механизм построения идентификаторов, стандартизированный всякими международными техническими комитетами. В рамках этого механизма различные сущности, объекты, концепты, алгоритмы, компании (и вообще что угодно, на самом деле) получают гарантированно уникальный однозначный идентификатор.
OID используется везде, где нужно сослаться на некую «вещь», которая определена где-то ещё. Например, если нужно указать используемый алгоритм, то записываем OID этого алгоритма вместо, скажем, строки или локального идентификатора.
Структура OID иерерахичная, в человекочитаемом виде их обычно представляют набором чисел, разделённых точкой, например, 1.2.840.113549.1.1.1 — OID алгоритма шифрования RSA. При этом OID 1.2.840.113549 является идентификатором компании RSA® Security Limited Liability Company (LLC), соответственно, этот алгоритм находится в «пространстве имён» этой компании, которая может создавать любые новые идентификаторы, начинающиеся с её OID (1.2.840.113549). Также каждый сегмент-число может иметь строковое представление, оно локально для конкретного «родительского» OID. Например, корневой сегмент 1 имеет альтернативное имя iso
, корневой сегмент 0 — itu-t
, корневой сегмент 2 — joint-iso-itu-t
, других корневых сегментов нет. Внутри корневого сегмента 1 определены дочерние сегменты: standard(0), registration-authority(1), member-body(2), identified-organization(3). Ну и так далее. В таком виде строятся OID в ASN.1 нотации: {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) rsaEncryption(1)}
. Существует ещё одна эквивалентная нотация — OID-IRI (Internationalized Resource Identifier), в неё идентификатор выглядит так: /ISO/Member-Body/US/113549/1/1/1
. Однако мы в этом тексте из всего этого великолепия будем пользоваться исключительно точечной нотацией: 1.2.840.113549.1.1.1.
В ASN.1 определён специальный тип OBJECT IDENTIFIER
для OID и он используется чрезвычайно широко в самых разных объектах, если вы в спецификации блока данных видите OBJECT IDENTIFIER
, то в этом блоке лежит OID.
В интернете есть специальный сайт oid-info.com, на котором в едином реестре сведено множество OID, например, для нашего идентификатора алгоритма шифрования RSA адрес страницы: http://oid-info.com/get/1.2.840.113549.1.1.1 . Вы можете им пользоваться, если вам встретится незнакомый OID, или же вы захотите узнать числовое представление идентификатора, указанного в виде строки. Дальше я в тексте буду стараться указывать OID разных сущностей в виде ссылки на соответствующую страницу oid-info.com.
Distinguished Name (DN)¶
В русской литературе DN часто переводят как отличающееся имя, но это выглядит и звучит так мерзко, что я им не буду пользоваться, а буду всегда писать просто DN.
Понятие Distinguished Name (DN) пришло из LDAP, по сути это способ записи имени некоторого объекта/субъекта/сущности в строго структурированном виде.
DN представляет собой кортеж (то есть упорядоченную последовательность) из нескольких Relative DN (RDN), а каждый RDN состоит из пары значений AttributeType
(тип атрибута) и AttributeValue
(значение атрибута), при этом AttributeType
является OID, а в AttributeValue
чаще всего находится значение какого-нибудь из строковых типов.
Формальное определение DN даётся в RFC 5280 в таком виде:
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue AttributeTypeAndValue ::= SEQUENCE { type AttributeType, value AttributeValue } AttributeType ::= OBJECT IDENTIFIER AttributeValue ::= ANY -- DEFINED BY AttributeType DirectoryString ::= CHOICE { teletexString TeletexString (SIZE (1..MAX)), printableString PrintableString (SIZE (1..MAX)), universalString UniversalString (SIZE (1..MAX)), utf8String UTF8String (SIZE (1..MAX)), bmpString BMPString (SIZE (1..MAX)) }
Строго говоря, каждый RelativeDistinguishedName представляет собой не пару AttributeType и AttributeValue, а множество таких пар. Несколько пар в рамках одного RDN называется multi-valued RDN и теоретически может встретиться в реальной жизни, но мы пока оставим эту тему за рамками статьи.
В отличие от LDAP, в X.509 нет единого общепринятого способа сериализации DN в строковое значение (и обратно), поэтому различные программы и библиотеки придумывают свои способы. Но общий принцип такого кодирования примерно одинаковый: каждый RDN кодируется в виде строки KEY=VALUE
, а такие пары ключ-значение собираются в строку при помощи разделителя, например, A1=V1; A2=V2; A3=V3
. Во входных данных openssl используется нотация в такой форме:
/C=RU/ST=NSO/L=Novosibirsk/O=Regolit/CN=Sergey Stolyarov
При интерпретации такой строки каждый сегмент вида /type=value
декодируется в соответствующее бинарное значение. Типы атрибутов C
, L
, O
и некоторые другие определены в стандарте и декодируются в соответствующие OID. При этом можно сразу указать нужный OID. Например, вот эквивалентное представление DN:
/2.5.4.6=RU/2.5.4.8=NSO/L=Novosibirsk/O=Regolit/CN=Sergey Stolyarov
Здесь мы заменили C/Country
на его OID 2.5.4.6, а ST/stateOrProvinceName
— на OID 2.5.4.8. Также можно писать «длинные» типы атрибутов, например, вот тоже эквивалентное представление DN:
/Country=RU/stateOrProvinceName=NSO/L=Novosibirsk/O=Regolit/CN=Sergey Stolyarov
Все три строки будут в итоге сконвертированы в одинаковый бинарный код, представляющий DN.
Важное замечание
Последовательность компонентов в DN имеет значение,
/C=RU/L=Novosibirsk
и/L=Novosibirsk/C=RU
— это разные DN.О конкретных типах атрибутов я расскажу ниже в разделе, где это будет более уместно и логично.
При выводе DN в текстовом виде openssl использует другую нотацию, что, впрочем, вполне в духе программы:
C=RU, ST=NSO, L=Novosibirsk, O=Regolit, CN=Sergey Stolyarov
AlgorithmIdentifier¶
Под типом AlgorithmIdentifier почти всегда подразумевается подобная структура:
AlgorithmIdentifier ::= SEQUENCE { algorithm OBJECT IDENTIFIER, parameters ANY DEFINED BY algorithm OPTIONAL }
Это последовательность из двух полей: идентификатор (OID) алгоритма и опциональное поле с параметрами алгоритма. Названия конкретных полей могут различаться, но суть именно такая.
Генерация закрытого ключа¶
Первым этапом создания сертификата является генерация нового закрытого ключа на основе случайных данных. Я специально выделил это в отдельный пункт, чтобы вы смогли больше понять, как всё устроено. В других инструкциях сразу начинают с этапа создания certificate signing request (CSR), но мы к нему придём более последовательным путём.
Ключ можно дополнительно зашифровать симметричным шифром, используя отдельный пароль. Это дополнительный слой защиты для такого важного объекта: даже если злоумышленник получит доступ к файловой системе, он найдёт только зашифрованный объект. При использовании такого ключа необходимо ввести пароль при старте программы или в другой момент, когда он требуется.
Начнём с выбора криптографического алгоритма. Изначально он был ограничен двумя — RSA и DSA, позднее к ним добавились алгоритмы на эллиптических кривых (ECC).
Закрытый ключ RSA¶
Классическая команда для создания ключа RSA длиной 512 бит в файле rsa512_1.pem
выглядит так:
[[email protected]]% openssl genrsa -out rsa512_1.pem 512 Generating RSA private key, 512 bit long modulus (2 primes) ...+++++++++++++++++++++++++++ ........+++++++++++++++++++++++++++ e is 65537 (0x010001)
Результатом будет файл с одним PEM-блоком с меткой RSA PRIVATE KEY
:
-----BEGIN RSA PRIVATE KEY----- MIIBOgIBAAJBALCwLuqdlD0h/ZiNd1iORH5Zgbz6Una9iAb1uT2+5+VKAHjC53RC p6RG95VKVvQFhKic78Wy0hvxTbVbPGABS5MCAwEAAQJAa+OJInYKWLHyuj5Xy9lD dauODykDRcJB144gCNYTn+vmIvaUpe15DCCLpOe1KtkowFbS8HKsWHgAL28zn3nx 0QIhAN0qYrvboEMJJkHmbYIOEyheS1S2xMHD1EsR8YdyU92/AiEAzIRtP52v8trb xpm+VGbvqdoqyuaRC2yElpt8AjBC7y0CIQCaLopWXG4FTcOV/YYqPJWuds4daK0S R+sfyoqO2m0NEQIgBSCQyJaAcbsw5VK3ZdBK09xHVFzhaALpdAkj274v/2UCIEM3 WFaS5azuCXD53wMrJEC5GlqjPHfXbm6Zssyi15xh -----END RSA PRIVATE KEY-----
Внутри этого блока закодирован объект в формате, описанном в стандарте PKCS#1. PKCS#1 описывает алгоритм RSA, на данный момент его актуальный текст опубликован в виде RFC 3447. В стандарте описаны бинарные структуры ключей, именно в этом формате их создаёт команда openssl genrsa
.
Вот как выглядит разобранный на составные закрытый ключ RSA в формате PKCS#1 (я сократил слишком длинные строки для читабельности, помним, что почти все объекты в нашей области закодированы в DER, который позволяет проводить автоматический разбор структуры):
[[email protected]]% openssl asn1parse -i -in rsa512_1.pem 0:d=0 hl=4 l= 314 cons: SEQUENCE 4:d=1 hl=2 l= 1 prim: INTEGER :00 7:d=1 hl=2 l= 65 prim: INTEGER :B12CA08DF04E4D4AD4149E789...skip...B0D7E0644D7186EC5C6B7D 74:d=1 hl=2 l= 3 prim: INTEGER :010001 79:d=1 hl=2 l= 64 prim: INTEGER :7B5CD76DFD24882C9F74ADAEC...skip...17D123C69EE06BFEAFF01 145:d=1 hl=2 l= 33 prim: INTEGER :D543F842828515251F369D601752BC08602383B4A15A34D6C705877D791B54DB 180:d=1 hl=2 l= 33 prim: INTEGER :D4AD434262295D2BB2F3DE825AF8F6697B72D0902694112773A72EB36161C487 215:d=1 hl=2 l= 32 prim: INTEGER :4D84381F8CAB6CC522744A7D9BDCA1A5F5B3D2F27BD77AEF3A45E33A93238113 249:d=1 hl=2 l= 32 prim: INTEGER :56A1DD6C0520645B90A1D659B34506DB20F63C0EFC280474D59F9C5E65A4B5B1 283:d=1 hl=2 l= 33 prim: INTEGER :B5F1F5F4CA1771A6990851CFBCBDF5E2B6A032AD02AFA8AF914E47A73E8AE0DB
Для бо́льшей наглядности я использовал аргумент -i
, он отделяет вложенные блоки отступом. В данном случае мы видим, что внутри файла последовательно записаны девять чисел, в RFC 3447, раздел A.1.2 даётся такое ASN.1 определение этой структуры:
RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q exponent1 INTEGER, -- d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER, -- (inverse of q) mod p otherPrimeInfos OtherPrimeInfos OPTIONAL }
Первое поле — версия данных, это стандартная практика, а дальше идут восемь чисел, определяющих закрытый ключ RSA. Программы, использующие закрытый ключ, разбирают эти данные на числа и дальше пользуются ими для работы.
Команда openssl genrsa
не умеет записывать ключ сразу в формате DER. Если вам нужен именно он, можно сконвертировать ключ из rsa512_1.pem
в файл rsa512_1.der
вот такой командой:
[[email protected]]% openssl rsa -in rsa -in rsa512_1.pem -outform DER -out rsa512_1.der writing RSA key
Аргумент -outform DER
как раз задаёт выходной формат данных, если его не указывать, то подразумевается -outform PEM
.
──────────────────
Как я уже упоминал выше, данные закрытого ключа можно зашифровать симметричным алгоритмом. Для этого нужно в команду openssl genrsa
передать дополнительный аргумент, после чего программа попросит вас ввести два раза пароль, которым будут зашифрованы сгенерённые данные. Например, чтобы зашифровать ключ шифром AES-128-CBC
, нужно выполнить команду:
[[email protected]]% openssl genrsa -out rsa512_1_e.pem -aes128 512 Generating RSA private key, 512 bit long modulus ...............++++++++++++ ......++++++++++++ e is 65537 (0x10001) Enter pass phrase for rsa512_1_e.pem: Verifying - Enter pass phrase for rsa512_1_e.pem:
Результирующий файл rsa512_1_e.pem
содержит один PEM-блок с такой же меткой RSA PRIVATE KEY
, однако к блоку добавлены дополнительные атрибуты (они описаны в RFC 1421), указывающие, что данные внутри зашифрованы:
-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,49FDC7C579C43CA931FAC9BCB6AD829B SORCkWmb3Nv1Zzge5+kDLWbNDBdVl4w5bDQaGY4ebArDTqKPHAwQMJ2Kv7H2XUEo U94CJ8BT1X9Ycn4CCawY7DQoUn17TfHHGpW7Kn8Nzh6k7CWKzsvdQHxfnKM5QRN7 00COHepj2fAN5+UIT+2Md3c4QcKCjLfA4sIzTLBrrNl7HdwUs9cFEyKxl0j4DkzE t0OytYL2o/yrLY90QHohLsqMVDRr2I+O+QtaJm/3zSbGAJmsEDYkt9InG9MfikYd /HTCV3Mw/uLVLUEBbHT48lFUNZcaYD2a17r0hinsmNIwY9PVNfc4zt5WRKFYV+MV Xw44d0cM8aA9dLx76VGSbyshBR/m2DRvwvUonjH7Sv/zKe6mz990NZ4e6pgwWCek XmLu+z9k7hOERYwMJC0vweLGlxR3Cu1fErcbHiDfIGI= -----END RSA PRIVATE KEY-----
Расшифровать такой файл можно такой командой (нужно знать пароль, которым зашифрованы данные):
[[email protected]]% openssl rsa -in rsa512_1_e.pem -out rsa512_1.pem Enter pass phrase for rsa512_1_e.pem: writing RSA key
Ну и также вы можете зашифровать уже имеющийся закрытый ключ RSA (rsa512_1.pem
) командой openssl rsa
(воспользуемся алгоритмом AES-192-CBC):
[[email protected]]% openssl rsa -in rsa512_1.pem -out rsa512_1_e.pem -aes192 writing RSA key Enter PEM pass phrase: Verifying - Enter PEM pass phrase:
Замечания
Список доступных алгоритмов для шифрования зависит от версии и сборки openssl. Базовые алгоритмы типа
AES-128-CBC
,AES-192-CBC
,DES-CBC
,DES-EDE3-CBC
и несколько других обычно есть везде.Как вы могли заметить, для создания ключей RSA используется команды
openssl genrsa
, а для модификации —openssl rsa
. Вторая команда также может использоваться для конвертации PEM в DER и обратно, генерации открытого ключа из закрытого и некоторых других операций.Зашифрованный ключ формата PKCS#1 нельзя хранить в виде DER-файла. Если вы попытаетесь сконвертировать закрытый RSA-ключ командой
openssl rsa
, то программа спросит у вас пароль и запишет DER-файл с расшифрованным ключом внутри.Никогда не используйте закрытый RSA ключ длиной менее 2048 бит, я в статье использую 512 только для демонстрации!
Закрытый ключ эллиптической криптосистемы¶
Важное замечание.
Разные программы и библиотеки поддерживают разные эллиптические кривые, и несмотря на то что openssl понимает их очень много, лишь очень ограниченный набор применим для реальных ситуаций, например, для SSL/TLS. См., например, таблицу совместимости кривых и TLS-библиотек: https://en.wikipedia.org/wiki/Comparison_of_TLS_implementations#Supported_elliptic_curves. Я буду использовать в примерах лишь те кривые, которые имеют максимально возможную поддержку среди библиотек.
Для создания закрытого ключа эллиптической криптосистемы (EC-ключа) используется команда openssl ecparam
:
[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout -out prime256v1-eckey.pem
Аргумент -noout
нужен для того, чтобы в результирующий файл был записан только сгенерённый закрытый ключ, без параметров использованной эллиптической кривой в виде отдельного PEM-блока. Название используемой кривой указываем в аргументе -name prime256v1
.
Команда openssl ecparam
создаёт ключ в формате, описанном в стандарте RFC 5915, в PEM-файле это блок с маркером EC PRIVATE KEY
, его структура описывается вот таким упрощённым ASN.1-определением:
ECPrivateKey ::= SEQUENCE { version INTEGER, privateKey OCTET STRING, parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, publicKey [1] BIT STRING OPTIONAL }
В нём два обязательных поля: version и privateKey, а также два опциональных: parameters — с параметрами эллиптической кривой и publicKey — с открытым ключом.
Посмотрим на созданный ключ, он записывается в PEM-блок с маркером EC PRIVATE KEY
:
-----BEGIN EC PRIVATE KEY----- MHcCAQEEIP6zRS0PbMX8QZZb7KMc0Us/XFa/NMDWVgXLuVEeas0LoAoGCCqGSM49 AwEHoUQDQgAEFoLqkuY6BGIKFrQE7B1ui+rrEit4vLr34toMd2IX7tKwn+WlRRcZ CfvZUi/8NVK5T3ru2Y0RpmW5qC1QUGPWcA== -----END EC PRIVATE KEY-----
Если воспользоваться командой openssl asn1parse
, увидим структуру данных внутри этого блока:
[[email protected]]% openssl asn1parse -i -in prime256v1-eckey.pem 0:d=0 hl=2 l= 119 cons: SEQUENCE 2:d=1 hl=2 l= 1 prim: INTEGER :01 5:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:FEB3452D0F6CC5FC41965BECA31CD14B3F5C56BF34C0D65605CBB9511E6ACD0B 39:d=1 hl=2 l= 10 cons: cont [ 0 ] 41:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 51:d=1 hl=2 l= 68 cons: cont [ 1 ] 53:d=2 hl=2 l= 66 prim: BIT STRING
Видим, что в первом поле записана версия (число 01
), во втором поле — закрытый ключ в виде байтовой строки, в третьем находится блок с параметрами в виде идентификатора кривой (в форме OID, которое преобразуется программой в человекочитаемый формат prime256v1
) и в конце — открытый ключ.
При генерации ключа вместо prime256v1
можно указать название другой эллиптической кривой, openssl их знает очень много, полный список можно вывести командой openssl ecparam -list_curves
:
[[email protected]]% openssl ecparam -list_curves secp112r1 : SECG/WTLS curve over a 112 bit prime field secp112r2 : SECG curve over a 112 bit prime field secp128r1 : SECG curve over a 128 bit prime field ...skip... brainpoolP512t1: RFC 5639 curve over a 512 bit prime field SM2 : SM2 curve over a 256 bit prime field
За каждым названием типа secp112r1
или secp112r1
скрыт набор параметров, задающих эту кривую, и вы можете их включить в закрытый ключ вместо предопределённого названия кривой. Это делается добавлением аргумента -param_enc explicit
в вызов команды openssl ecparam
:
[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout -param_enc explicit -out prime256v1-eckey-full.pem
Результирующий файл prime256v1-eckey-full.pem
получается заметно больше:
-----BEGIN EC PRIVATE KEY----- MIIBaAIBAQQg90po7E2F5ushBs6832qyZDKu34YUL6XnQgTzICeYgbaggfowgfcC AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA//////////////// MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8 YyVRAgEBoUQDQgAEYYr/jahQc8DXGycDUG+4uYPPvAM447vdt6mAtNOEisSPeKVW CTBUZ+Bm/rTzuDqIkRVFiGibIh/irFnK3gTvAg== -----END EC PRIVATE KEY-----
А его структура уже сложнее — вместо идентификатора кривой появился новый блок с её полными параметрами:
[[email protected]]% openssl asn1parse -i -in prime256v1-eckey-full.pem 0:d=0 hl=4 l= 360 cons: SEQUENCE 4:d=1 hl=2 l= 1 prim: INTEGER :01 7:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:F74A68EC4D85E6EB2106CEBCDF6AB26432AEDF86142FA5E74204F320279881B6 41:d=1 hl=3 l= 250 cons: cont [ 0 ] 44:d=2 hl=3 l= 247 cons: SEQUENCE 47:d=3 hl=2 l= 1 prim: INTEGER :01 50:d=3 hl=2 l= 44 cons: SEQUENCE 52:d=4 hl=2 l= 7 prim: OBJECT :prime-field 61:d=4 hl=2 l= 33 prim: INTEGER :FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF 96:d=3 hl=2 l= 91 cons: SEQUENCE 98:d=4 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC 132:d=4 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B 166:d=4 hl=2 l= 21 prim: BIT STRING 189:d=3 hl=2 l= 65 prim: OCTET STRING [HEX DUMP]:046B17D1F2E12C4247F8...skipped...CBB6406837BF51F5 256:d=3 hl=2 l= 33 prim: INTEGER :FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 291:d=3 hl=2 l= 1 prim: INTEGER :01 294:d=1 hl=2 l= 68 cons: cont [ 1 ] 296:d=2 hl=2 l= 66 prim: BIT STRING
──────────────────
Для любой именованной кривой, которую знает openssl, вы можете посмотреть её параметры в человекочитаемом формате:
[[email protected]]% openssl ecparam -name prime256v1 -text -param_enc explicit -noout Field Type: prime-field Prime: 00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00: 00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff: ff:ff:ff A: 00:ff:ff:ff:ff:00:00:00:01:00:00:00:00:00:00: 00:00:00:00:00:00:ff:ff:ff:ff:ff:ff:ff:ff:ff: ff:ff:fc B: 5a:c6:35:d8:aa:3a:93:e7:b3:eb:bd:55:76:98:86: bc:65:1d:06:b0:cc:53:b0:f6:3b:ce:3c:3e:27:d2: 60:4b Generator (uncompressed): 04:6b:17:d1:f2:e1:2c:42:47:f8:bc:e6:e5:63:a4: 40:f2:77:03:7d:81:2d:eb:33:a0:f4:a1:39:45:d8: 98:c2:96:4f:e3:42:e2:fe:1a:7f:9b:8e:e7:eb:4a: 7c:0f:9e:16:2b:ce:33:57:6b:31:5e:ce:cb:b6:40: 68:37:bf:51:f5 Order: 00:ff:ff:ff:ff:00:00:00:00:ff:ff:ff:ff:ff:ff: ff:ff:bc:e6:fa:ad:a7:17:9e:84:f3:b9:ca:c2:fc: 63:25:51 Cofactor: 1 (0x1) Seed: c4:9d:36:08:86:e7:04:93:6a:66:78:e1:13:9d:26: b7:81:9f:7e:90
Здесь параметр -text
означает, что нужно вывести информацию в человекочитаемой форме, а -param_enc explicit
означает, что параметры кривой нужно вывести в явной форме без сокращённого до имени названия. -noout
отключает вывод собственно указанного объекта на экран.
Вы можете сравнить вывод этого и предыдущего листингов, чтобы убедиться, что они описывают один и тот же объект.
──────────────────
Параметры кривой можно также вывести в отдельный PEM-файл:
[[email protected]]% openssl ecparam -name prime256v1 -param_enc explicit -out prime256v1-curve.pem
Получается такой файл prime256v1-curve.pem
с PEM-блоком с маркером EC PARAMETERS
:
-----BEGIN EC PARAMETERS----- MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP////////// /////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6 k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9+ kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz ucrC/GMlUQIBAQ== -----END EC PARAMETERS-----
И мы опять можем через asn1parse посмотреть его структуру:
[[email protected]]% openssl asn1parse -i -in prime256v1-curve.pem 0:d=0 hl=3 l= 247 cons: SEQUENCE 3:d=1 hl=2 l= 1 prim: INTEGER :01 6:d=1 hl=2 l= 44 cons: SEQUENCE 8:d=2 hl=2 l= 7 prim: OBJECT :prime-field 17:d=2 hl=2 l= 33 prim: INTEGER :FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF 52:d=1 hl=2 l= 91 cons: SEQUENCE 54:d=2 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC 88:d=2 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B 122:d=2 hl=2 l= 21 prim: BIT STRING 145:d=1 hl=2 l= 65 prim: OCTET STRING [HEX DUMP]:046B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F......406837BF51F5 212:d=1 hl=2 l= 33 prim: INTEGER :FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 247:d=1 hl=2 l= 1 prim: INTEGER :01
──────────────────
Если у вас есть файл с параметрами эллиптической кривой, то вы можете его передать в команду openssl ecparam
и сгенерировать ключ на этой кривой, даже если вашей версии openssl ничего про неё не известно. Вот полезный пример, как создать ключ для алгоритма ГОСТ 34.10-2012 в версии openssl, которая по умолчанию не содержит определения этой кривой. Параметры я записал в файл id-tc26-gost-3410-2012-512-paramSetA.pem, вы можете его скачать и использовать для создания ключа таким образом:
[[email protected]]% openssl ecparam -in id-tc26-gost-3410-2012-512-paramSetA.pem -genkey -noout -out gost-eckey.pem
Если вы запускали все вышеперечисленные команды, у вас должно быть создано два закрытых ключа: secp160k1-eckey.pem
и gost-eckey.pem
. Если посмотрите размеры этих файлов, то увидите, что в первом байтов в несколько раз меньше (922 байта против 174):
[[email protected]]% ls -l prime256v1-eckey.pem gost-eckey.pem -rw------- 1 sigsergv sigsergv 922 сен 1 23:56 gost-eckey.pem -rw------- 1 sigsergv sigsergv 227 сен 1 23:48 prime256v1-eckey.pem
Это происходит потому, что для второго ключа (gost-eckey.pem
) мы явным образом указали все параметры эллиптической кривой, в то время как для первого — только название предопределённой. Разница особенно заметна, если сравнить содержимое через asn1parse:
[[email protected]]% openssl asn1parse -i -in prime256v1-eckey.pem 0:d=0 hl=2 l= 119 cons: SEQUENCE 2:d=1 hl=2 l= 1 prim: INTEGER :01 5:d=1 hl=2 l= 32 prim: OCTET STRING [HEX DUMP]:FEB3452D0F6CC5FC41965BECA31CD14B3F5C56BF34C0D65605CBB9511E6ACD0B 39:d=1 hl=2 l= 10 cons: cont [ 0 ] 41:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 51:d=1 hl=2 l= 68 cons: cont [ 1 ] 53:d=2 hl=2 l= 66 prim: BIT STRING [[email protected]]% openssl asn1parse -i -in gost-eckey.pem 0:d=0 hl=4 l= 631 cons: SEQUENCE 4:d=1 hl=2 l= 1 prim: INTEGER :01 7:d=1 hl=2 l= 64 prim: OCTET STRING [HEX DUMP]:0310F98B02AAC16490DCBD61...skipped...E4589F44F219EFFF4 73:d=1 hl=4 l= 422 cons: cont [ 0 ] 77:d=2 hl=4 l= 418 cons: SEQUENCE 81:d=3 hl=2 l= 1 prim: INTEGER :01 84:d=3 hl=2 l= 76 cons: SEQUENCE 86:d=4 hl=2 l= 7 prim: OBJECT :prime-field 95:d=4 hl=2 l= 65 prim: INTEGER :FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF...skipped...FFFFFFFFFFFFDC7 162:d=3 hl=3 l= 132 cons: SEQUENCE 165:d=4 hl=2 l= 64 prim: OCTET STRING [HEX DUMP]:FFFFFFFFFFFFFFFFFFFFFFFFFFFF...skipped...FFFFFFFDC4 231:d=4 hl=2 l= 64 prim: OCTET STRING [HEX DUMP]:E8C2505DEDFC86DDC1BD0B2...skipped...4761503190785A71C760 297:d=3 hl=3 l= 129 prim: OCTET STRING [HEX DUMP]:04000000000000...skipped...5B889A589CB5215F2A4 429:d=3 hl=2 l= 65 prim: INTEGER :FFFFFFFFFFFFFFFFFF...skipped...11F10B275 496:d=3 hl=2 l= 1 prim: INTEGER :01 499:d=1 hl=3 l= 133 cons: cont [ 1 ] 502:d=2 hl=3 l= 130 prim: BIT STRING
Замечание
Команда
openssl ecparam
умеет сама записывать создаваемый ключ в формате DER, для этого нужно указать аргумент-outform DER
:[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout -outform DER -out prime256v1-eckey.der
Обратите внимание, что этот бинарный файл
prime256v1-eckey.der
по-прежнему подчиняется RFC 5915, это НЕ закодированный в DER PKCS#8-ключ. Если вам нужен бинарный PKCS#8-ключ, смотрите раздел Конвертация закрытых ключей в формат PKCS#8 и обратно.
──────────────────
Как и в случае RSA, вы можете зашифровать создаваемый закрытый ключ (только внутри PEM, шифрование DER не поддерживается), однако для этого нужно использовать сразу две соединённые команды, если хотите сгенерённый ключ зашифровать сразу при создании:
[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout | openssl ec -aes-128-cbc -out prime256v1-eckey-e.pem
──────────────────
Для операций над ключами эллиптических криптосистем существует отдельная команда openssl ec
, она по сути аналогична openssl rsa
, только работает для другого типа ключей. Например, конвертация ключа из формата PEM в DER делается так:
[[email protected]]% openssl ec -in prime256v1-eckey.pem -outform DER -out prime256v1-eckey.der read EC key writing EC key
А шифрование закрытого ключа — так:
[[email protected]]% openssl ec -in prime256v1-eckey.pem -aes192 -out prime256v1-eckey-e.pem read EC key writing EC key Enter PEM pass phrase: Verifying - Enter PEM pass phrase:
Закрытые ключи в формате PKCS#8¶
Важное замечание насчёт PKCS#8.
Ключи в данном формате могут до сих пор считаться несколько экзотичными и некоторые библиотеки или программы их не понимают. Но важно понимать, что это точно такие же закрытые ключи, только в специальном «универсальном» формате-контейнере.
Выше мы видели, что форматы данных для RSA и EC-ключей совершенно разные, а отличить их друг от друга можно только через разные PEM-маркеры. Если же ключи представлены в DER-файлах, распознать их можно только эмпирическим путём, пытаясь разобрать данные для разных спецификаций.
Эту ситуацию пытается разрешить стандарт PKCS#8, он описывает способ представления закрытых ключей для произвольных алгоритмов, а также способ шифрования данных ключа.
Структура закрытого ключа в формате PKCS#8 очень простая и описывается такой ASN.1-схемой (я её упростил для читаемости):
PrivateKeyInfo ::= SEQUENCE { version Version, privateKeyAlgorithm PrivateKeyAlgorithmIdentifier, privateKey PrivateKey, attributes [0] IMPLICIT Attributes OPTIONAL } PrivateKeyAlgorithmIdentifier ::= SEQUENCE { algorithm AlgorithmIdentifier, parameters AlgorithmParameters OPTIONAL }
По сути это набор из как минимум трёх полей: версии синтаксиса (сейчас там может быть только 0), идентификатора алгоритма закрытого ключа (см. Типы данных → AlgorithmIdentifier) и собственно данных закрытого ключа.
Идентификатор алгоритма (privateKeyAlgorithm) представляет собой набор из одного обязательного поля algorithm, в котором записывается OID (см. Типы данных → Object identifier (OID)), и опционального поля с параметрами алгоритма.
Обычно с конкретным идентификатором алгоритма закрытого ключа связан способ его кодирования, именно в таком формате должны быть записаны данные в поле privateKey.
──────────────────
Для создания закрытых ключей в формате PKCS#8 используется команда openssl genpkey
. Например, для создания RSA-ключа длиной 512 бит команда выглядит так (ключ записывается в файл rsa512_8.pem
):
[[email protected]]% openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:512 -out rsa512_8.pem
А вот как создать ключ для эллиптического алгоритма на кривой secp160k1 и сохранить его в файл secp160k1_8.pem
:
[[email protected]]% openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:prime256v1 -pkeyopt ec_param_enc:named_curve -out prime256v1-eckey-8.pem Enter PEM pass phrase: Verifying - Enter PEM pass phrase:
В обоих примерах создаётся файл в формате PEM, причём с одинаковыми маркерами. Вот как выглядит файл rsa512_8.pem
:
-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCaqCEC9Y6I3VC8 +QtqA6oD6vxvEJczu+YV5Kn8b7CvcqWBfXI3RCG8VB/Hbt1cj5n0jc266d/XDimT ......skipped...... G0SVctomV0+dx5azDC5Dj1Mt8xLgnkn6924yPLGLomGGVzsCpzxyqND7g4bU5yQV XILO58GSBmyOuWI7+EFb7s6N -----END PRIVATE KEY-----
А вот файл prime256v1-eckey-8.pem
, для его создания мы указали аргумент -pkeyopt ec_param_enc:named_curve
, поэтому полные параметры кривой в него не включены, а только имя:
-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgIcYPIBeg/FEzbxU7 gfUTGkSYULyfLAErmi/BWXUCq6+hRANCAARULnWxbWLN1bEP2l4tKHo8j4TjMU54 pldrPAXxlsRTG1A68AP8w+ATR3SAKGauy25+fTdgbk3gn2TbBXyoM3IE -----END PRIVATE KEY-----
Если запустить openssl asn1parse
, то можно увидеть внутреннюю структуру этих данных, например:
[[email protected]]% openssl asn1parse -i -in prime256v1-eckey-8.pem 0:d=0 hl=3 l= 135 cons: SEQUENCE 3:d=1 hl=2 l= 1 prim: INTEGER :00 6:d=1 hl=2 l= 19 cons: SEQUENCE 8:d=2 hl=2 l= 7 prim: OBJECT :id-ecPublicKey 17:d=2 hl=2 l= 8 prim: OBJECT :prime256v1 27:d=1 hl=2 l= 109 prim: OCTET STRING [HEX DUMP]:306B020101042021C60F...skipped...D37606E4DE09F64DB057CA8337204
Мы видим три поля: версию (со значением 0
), идентификатор алгоритма, состоящий из OID 1.2.840.10045.2.1 (обозначается идентификатором id-ecPublicKey
) и OID параметра 1.2.840.10045.3.1.7 именованной эллиптической кривой (обозначается идентификатором prime256v1
).
Если при создании ключа указать аргумент -pkeyopt ec_param_enc:explicit
, то в поле параметра алгоритма будут стоять параметры кривой вместо идентификатора prime256v1
.
──────────────────
Команда genpkey
позволяет также создавать зашифрованные ключи, однако по необъяснимым причинам позволяет это делать только в формате PEM, а чтобы преобразовать зашифрованный ключ в DER, нужно использовать команду openssl pkcs8
, о которой я расскажу в следующем разделе.
Зашифрованный ключ создаётся при указании аргумента с алгоритмом симметричного шифрования (в данном случае -aes-128-cbc
):
[[email protected]]% openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -aes-128-cbc -out rsa512_8_e.pem .....+++++ ................+++++ Enter PEM pass phrase: Verifying - Enter PEM pass phrase:
Конвертация закрытых ключей в формат PKCS#8 и обратно¶
В принципе, все современные библиотеки и платформы должны корректно принимать ключи в формате PKCS#8, однако в некоторых из них требуют ключ только в традиционном формате для данного алгоритма (например, PKCS#1 для RSA-ключей). В openssl есть команда для конвертации закрытых ключей между PKCS#8 и традиционным форматом и обратно.
Например, для преобразования файла RSA-ключа rsa512_1.der
из PKCS#1 DER в PKCS#8 PEM без симметричного шифрования используется такая команда:
[[email protected]]% openssl pkcs8 -topk8 -nocrypt -in rsa512_1.der -inform DER
Если же вы хотите зашифровать ключ, замените аргумент -nocrypt
на -v2 aes128
(вместо aes128
можно также использовать aes256
или des3
):
[[email protected]]% openssl pkcs8 -topk8 -v2 aes128 -in rsa512_1.der -inform DER Enter Encryption Password: Verifying - Enter Encryption Password:
Команда запросит новый пароль и его подтверждение.
Важное замечание насчёт пароля.
Симметричный алгоритм обычно требует в качестве ключа шифрования набор байтов строго определённой длины, например, 128 бит в случае AES-128-CBC, однако пользователю разрешено ввести произвольный пароль, длина которого отличается от требуемых 128 бит. Поэтому пароль сначала преобразуется специальными методами в набор байтов нужной длины и только потом этот набор байтов используется в качестве ключа.
Такая процедура называется KDF — key derivation function, в русскоязычной терминологии — функция формирования ключа. И если выбранный алгоритм симметричного шифрования использует очень короткий ключ, ваш длинный «безопасный» пароль будет преобразован в короткий ключ, который злоумышленник сможет легко подобрать (то есть ему нужно подобрать существенно менее длинный ключ, чем пароль, из которого он генерируется).
В некоторых вариантах openssl, например, в libressl из macos по умолчанию используется очень слабый алгоритм с очень коротким ключом, поэтому всегда указывайте алгоритм шифрования, как минимум это должен быть AES-128-CBC (что делается аргументом
-v2 aes128
).Ну а вообще вся эта криптография с паролем соответствует PKCS#5, последняя версия которого описана в RFC 2898. Аргумент
-v2
включает как раз её (PKCS#5 v2.0), однако не все программы могут такие данные принимать и может понадобиться использовать прошлую версию (PKCS#5 v1.5), но это уже детали, о которых я тут не буду рассказывать.
По умолчанию результат выводится в терминал, чтобы вывести в файл rsa512_8_e.pem
, укажите аргумент -out rsa512_8_e.pem
:
[[email protected]]% openssl pkcs8 -topk8 -v2 aes128 -in rsa512_1.der -inform DER -out rsa512_8_e.pem
──────────────────
Если вы не хотите шифровать данные ключа, используйте аргумент -nocrypt
:
[[email protected]]% openssl pkcs8 -topk8 -nocrypt -in rsa512_1.der -inform DER -out rsa512_8.pem
❈ ❈ ❈
Для обратного преобразования из PKCS#8 в традиционный формат используется аргумент -traditional
:
[[email protected]]% openssl pkcs8 -nocrypt -in rsa512_8.pem -traditional
Обратите внимание, что я указал аргумент -nocrypt
, без него программа не сможет прочитать данные из файла с незашифрованным ключом. Если входной файл с зашифрованным ключом, этот аргумент не нужен. openssl автоматически определять тип данных внутри файла не умеет.
Хаос с генерацией ключей в openssl¶
В документации к openssl написано, что genpkey заменяет целый класс других команд типа genrsa, gendsa и других. Однако формат создаваемых файлов у genrsa и genpkey совершенно разный: первая генерирует RSA-ключ в формате PKCS#1, а вторая — PKCS#8. Но если указать вывод в DER, то genpkey создаёт не PKCS#8 в формате DER, а тоже PKCS#1 DER! Однако для эллиптических алгоритмов и PEM, и DER оба ключа создаются в формате PKCS#8.
Более того, в ряде команд нигде не указано, что результирующий файл будет записан в формате PKCS#8, например, этим грешит команда openssl req
, ниже я подробнее о ней расскажу.
Создание Certificate Signing Request¶
Жизненный цикл сертификата продолжается созданием запроса не подпись сертификата (Certificate Signing Request, CSR). По сути он является шаблоном, на основе которого удостоверяющий центр создаст и подпишет сертификат. CSR содержит обязательные личные данные заявителя, открытый ключ и дополнительные необязательные поля с другими данными. И всё это должно быть подписано закрытым ключом, который соответствует приложенному открытому.
Создание CSR в диалоговом режиме¶
Мы будем делать CSR на основе закрытого ключа, ранее созданного в файле prime256v1-eckey.pem
.
Для создания CSR используется команда openssl req
, обычно её запускают в интерактивном режиме, в котором она запрашивает у пользователя личные данные:
[[email protected]]% openssl req -new -key prime256v1-eckey.pem -out regolit-1.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) []:RU State or Province Name (full name) []:NSO Locality Name (eg, city) []:Novosibirsk Organization Name (eg, company) []:Regolit Organizational Unit Name (eg, section) []: Common Name (eg, fully qualified host name) []:test.regolit.com Email Address []:[email protected] Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []:123456 An optional company name []:
Стандартный набор полей:
- страна (Country Name) → RU, здесь вводится двухсимвольный код страны в соответствии с ISO 3166-1;
- область/регион (State or Province Name) → NSO, название области, края, провинции и так далее;
- название населённого пункта (Locality Name) → Novosibirsk, название города, посёлка и так далее;
- название организации (Organization Name) → Regolit, название организации;
- отдел организации (Organizational Unit Name) → название отдела в организации (я это поле не заполнял);
- общее имя (Common Name) → regolit.com, это по сути название сертификата, ранее сюда принято было записывать доменное имя сайта, подробнее об этом я расскажу позднее;
- электронная почта (Email Address) → [email protected], адрес электронной почты;
- опознавательный пароль (A challenge password) → 123456, опознавательный пароль, об этом я тоже расскажу отдельно.
Я указал закрытый ключ в аргументе -key secp160k1-eckey.pem
, в процессе генерации CSR в файле domain.csr
из него был извлечён открытый ключ, добавлен в CSR, добавлены остальные личные данные, и в итоге этим же закрытым ключом CSR был подписан.
Формат CSR описан в PKCS#10, последняя на текущий момент его версия — RFC 2986. Как и в случае с ключом, вы можете посмотреть структуру только что созданного файла командой openssl ans1parse
:
[[email protected]]% openssl asn1parse -dump -i -in domain.csr 0:d=0 hl=4 l= 341 cons: SEQUENCE 4:d=1 hl=3 l= 251 cons: SEQUENCE 7:d=2 hl=2 l= 1 prim: INTEGER :00 10:d=2 hl=3 l= 129 cons: SEQUENCE 13:d=3 hl=2 l= 11 cons: SET 15:d=4 hl=2 l= 9 cons: SEQUENCE 17:d=5 hl=2 l= 3 prim: OBJECT :countryName 22:d=5 hl=2 l= 2 prim: PRINTABLESTRING :RU 26:d=3 hl=2 l= 12 cons: SET 28:d=4 hl=2 l= 10 cons: SEQUENCE 30:d=5 hl=2 l= 3 prim: OBJECT :stateOrProvinceName 35:d=5 hl=2 l= 3 prim: UTF8STRING :NSO 40:d=3 hl=2 l= 20 cons: SET 42:d=4 hl=2 l= 18 cons: SEQUENCE 44:d=5 hl=2 l= 3 prim: OBJECT :localityName 49:d=5 hl=2 l= 11 prim: UTF8STRING :Novosibirsk 62:d=3 hl=2 l= 16 cons: SET 64:d=4 hl=2 l= 14 cons: SEQUENCE 66:d=5 hl=2 l= 3 prim: OBJECT :organizationName 71:d=5 hl=2 l= 7 prim: UTF8STRING :Regolit 80:d=3 hl=2 l= 25 cons: SET 82:d=4 hl=2 l= 23 cons: SEQUENCE 84:d=5 hl=2 l= 3 prim: OBJECT :commonName 89:d=5 hl=2 l= 16 prim: UTF8STRING :test.regolit.com 107:d=3 hl=2 l= 33 cons: SET 109:d=4 hl=2 l= 31 cons: SEQUENCE 111:d=5 hl=2 l= 9 prim: OBJECT :emailAddress 122:d=5 hl=2 l= 18 prim: IA5STRING :[email protected] 142:d=2 hl=2 l= 89 cons: SEQUENCE 144:d=3 hl=2 l= 19 cons: SEQUENCE 146:d=4 hl=2 l= 7 prim: OBJECT :id-ecPublicKey 155:d=4 hl=2 l= 8 prim: OBJECT :prime256v1 165:d=3 hl=2 l= 66 prim: BIT STRING 0000 - 00 04 16 82 ea 92 e6 3a-04 62 0a 16 b4 04 ec 1d .......:.b...... 0010 - 6e 8b ea eb 12 2b 78 bc-ba f7 e2 da 0c 77 62 17 n....+x......wb. 0020 - ee d2 b0 9f e5 a5 45 17-19 09 fb d9 52 2f fc 35 ......E.....R/.5 0030 - 52 b9 4f 7a ee d9 8d 11-a6 65 b9 a8 2d 50 50 63 R.Oz.....e..-PPc 0040 - d6 70 .p 233:d=2 hl=2 l= 23 cons: cont [ 0 ] 235:d=3 hl=2 l= 21 cons: SEQUENCE 237:d=4 hl=2 l= 9 prim: OBJECT :challengePassword 248:d=4 hl=2 l= 8 cons: SET 250:d=5 hl=2 l= 6 prim: UTF8STRING :123456 258:d=1 hl=2 l= 10 cons: SEQUENCE 260:d=2 hl=2 l= 8 prim: OBJECT :ecdsa-with-SHA256 270:d=1 hl=2 l= 73 prim: BIT STRING 0000 - 00 30 46 02 21 00 f2 e7-5d 8b b5 23 90 84 00 13 .0F.!...]..#.... 0010 - a7 39 f7 36 78 d1 31 dc-99 9c 90 0e 6c 1a 33 56 .9.6x.1.....l.3V 0020 - 81 b1 b0 0c b2 ab 02 21-00 90 7b 5d 8c 58 ec 06 .......!..{].X.. 0030 - f7 88 bf cd 19 22 20 29-d7 ff aa 19 09 0b c1 c1 ....." )........ 0040 - 66 94 18 39 9f 47 b4 0d-07 f..9.G...
Полную ASN.1-схему вы найдёте в RFC 2986. А я сейчас простыми словами её расскажу. Весь блок CSR состоит из трёх последовательно идущих элементов:
toBeSigned
- содержательная часть запроса с личными данными, открытым ключом и другими атрибутами, в RFC 2986 этот блок обозначается как
CertificationRequestInfo
; algorithm
- идентификатор (OID) алгоритма подписи, в нашем случае openssl знает, на какой именно объект ссылается OID и вместо его «сырого» представления (1.2.840.10045.4.3.2) подставляет «человеческое» название
ecdsa-with-SHA256
; signature
- собственно цифровая подпись содержательной части.
Структура блока CertificationRequestInfo
стандартная:
version
- номер версии;
subject
- структурированный блок с личными данными;
subjectPKInfo
- открытый ключ;
attributes
- опциональный набор дополнительных атрибутов.
Практически все поля, которые вы заполняли в диалоговом режиме, ушли в блок subject
, его тип — distinguished name и я про него писал подробно в разделе Типы данных → Distinguished Name (DN).
Значение поля опознавательный пароль (A challenge password) было записано в отдельный атрибут, детально оно в стандартах не определено и отдано на усмотрение удостоверяющим центрам. Оно не фигурирует в итоговом сертификате или в процессе его изготовления, однако может использоваться в отдельных административных процедурах самого центра. К примеру, для отзыва сертификата УЦ может потребовать от заявителя сообщить тот же самый пароль, который был передан в оригинальном CSR. Также обратите внимание, что это чисто информационное поле и оно никак не используется в криптографических операциях на протяжении всего жизненного цикла сертификата.
Замечание
Команда
openssl req
позволяет сгенерировать закрытый ключ вместе с запросом, для этого используется аргумент-newkey
, однако я намеренно этот случай не рассматриваю и явным образом отделяю этап создания закрытого ключа. Вы можете сами изучить этот режим, если хотите.При использовании
-newkey
закрытый ключ всегда создаётся в формате PKCS#8 и поэтому может потребовать принудительной конвертации в традиционный формат.
Создание CSR в командном режиме¶
У openssl req
есть командный режим, в котором CSR создаётся без интерактивного взаимодействия, а все требуемые значения передаются в командной строке, например, так:
[[email protected]]% openssl req -new -batch -key prime256v1-eckey.pem \ -subj '/C=RU/ST=NSO/L=Novosibirsk/O=Regolit/CN=test.regolit.com/[email protected]' \ -out regolit-2.csr
Результат будет практически тот же самый, только поле challengePassword в таком режиме передать невозможно.
В аргументе -subj
указываются личные данные в формате /type1=value1/type2=value2/type3=value3
, его я подробно разбирал в разделе Типы данных → Distinguished Name (DN). В качестве type1, type2 и т.п. могут выступать следующие значения:
C
илиcountryName
CN
илиcommonName
L
илиlocalityName
ST
илиstateOrProvinceName
O
илиorganizationName
OU
илиorganizationalUnitName
emailAddress
❈ ❈ ❈
Создание CSR в расширенном командном режиме¶
Возможности стандартного командного режима сильно ограничены и функциональность openssl по-настоящему раскрывается при использовании конфигурационных файлов. Привычный режим работы программы во многом определяется штатным конфигом, который устанавливается вместе с openssl. Например, интерактивный режим запроса личных данных настраивается именно в файле и вы можете написать свой собственный конфиг с собственными вопросами и параметрами.
Полный обзор возможностей этого режима заслуживает отдельной статьи, а здесь я лишь вкратце продемонстрирую, как использовать файлы для передачи аргументов в программу.
Вот готовый файл с полным набором данных для создания CSR, включая поле challengePassword.
[ req ] prompt = no distinguished_name = req_distinguished_name attributes = req_attributes [ req_distinguished_name ] C = RU ST = NSO L = Novosibirsk O = Regolit CN = test.regolit.com emailAddress = se[email protected] [ req_attributes ] challengePassword = 123456
Вы можете скачать его с моего гитхаба (domain-csr.cnf) в каталог с закрытым ключом, а потом запустить в это же каталоге такую команду:
[[email protected]]% openssl req -new -batch -config domain-csr.cnf -key prime256v1-eckey.pem -out regolit.csr
cnf-файлы мы ещё будем использовать позднее при создании сертификата. Пока лишь отмечу, что в реальной жизни вам вряд ли понадобится создание CSR таким способ, поскольку удостоверяющий центр обычно берёт из файла запроса очень ограниченный набор данных, фактически только имя и открытый ключ, а всё остальное берёт по другим каналом, например, через форму в кабинете заявителя на сайте УЦ.
Создание тестового удостоверяющего центра¶
Чтобы продемонстрировать весь жизненный цикл сертификата, мы должны также выступить в роли удостоверяющего центра (УЦ, Certification Authority, CA), который проверит переданный ему CSR и сгенерирует на его основе сертификат.
Для более полного понимания мы создадим два простейших УЦ: корневой и промежуточный. Вместе с подписанным сертификатом заявителя мы получим цепочку из трёх. И если корневой сертификат будет доверенным, автоматически будут и оба других.
Сразу важное замечание: описанный здесь процесс ни в коем случае не описывает принципы работы и технологии настоящего удостоверяющего центра, однако общие принципы переданы верно. Также в составе openssl есть команда
openssl ca
, которая в минимальной степени реализует некоторые технические операции удостоверяющего центра, однако это тоже тема отдельной статьи и в этом тексте я её рассматривать не буду.
Самоподписанный сертификат для корневого УЦ¶
Для корневого удостоверяющего центра мы должны создать отдельный ключ (demo-root-ca.key
) и самоподписанный сертификат (demo-root-ca.crt
). Сначала сгенерируем закрытый ключ (эллиптическая кривая secp384r1 ради разнообразия, она тоже поддерживается всеми библиотеками):
[[email protected]]% openssl ecparam -name secp384r1 -genkey -out demo-root-ca.key
Создать самоподписанный сертификат можно той же командой openssl req
, которой создаётся CSR, даже аргументы идентичны, только добавляется ещё аргумент -x509
:
[[email protected]]% openssl req -new -x509 -batch -days 1000 -key demo-root-ca.key -subj '/C=AQ/O=Penguin Co./CN=Demo Root CA' -out demo-root-ca.crt
Также мы добавили аргумент -days 1000
, указывающий, что нужно срок действия сертификата выставить в 1000 дней, начиная с текущей даты.
В аргументе -subj
передаётся DN поля subject, закодированный в виде строки, об этом формате я писал в разделе Типы данных → Distinguished Name (DN). А о типах атрибутов в нём я писал в разделе Создание CSR в командном режиме.
Просмотр сертификата¶
Просмотр сертификата — это, пожалуй, самая частая операция, которую вы будете делать. Так как первый сертификат мы сделали только что, то и команда для его просмотра в этом разделе. Вот как это выглядит на примере только что созданного сертификата demo-root-ca.crt
:
[[email protected]]% openssl x509 -text -noout -in demo-root-ca.crt Certificate: Data: Version: 3 (0x2) Serial Number: 01:67:67:4d:23:ef:1e:91:4d:41:b1:df:0b:97:52:70:80:34:a9:03 Signature Algorithm: ecdsa-with-SHA256 Issuer: C = AQ, O = Penguin Co., CN = Demo Root CA Validity Not Before: Sep 1 08:00:26 2020 GMT Not After : May 29 08:00:26 2023 GMT Subject: C = AQ, O = Penguin Co., CN = Demo Root CA Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (384 bit) pub: 04:2d:3a:84:fe:c1:d8:54:ee:93:55:5c:8b:33:15: 2f:bb:46:44:e3:8e:b7:63:b4:17:9c:1b:c3:43:83: c2:ce:ab:6e:bd:01:7b:92:e0:cf:12:f1:0f:ab:db: bc:9f:35:38:da:b8:c0:b1:2e:40:e2:5f:c9:ff:61: 06:d8:2f:95:1d:41:64:ec:eb:8a:da:37:94:0c:41: 12:54:bb:26:5a:44:97:3f:c7:a7:94:e0:67:ad:38: 14:f3:0d:83:f4:5c:0b ASN1 OID: secp384r1 NIST CURVE: P-384 X509v3 extensions: X509v3 Subject Key Identifier: 89:12:EA:73:BC:F2:E2:43:7F:E0:A4:54:A6:60:AE:B7:4D:EA:06:0C X509v3 Authority Key Identifier: keyid:89:12:EA:73:BC:F2:E2:43:7F:E0:A4:54:A6:60:AE:B7:4D:EA:06:0C X509v3 Basic Constraints: critical CA:TRUE Signature Algorithm: ecdsa-with-SHA256 30:65:02:31:00:ba:85:60:9a:18:02:3f:c9:ac:10:ff:f4:7a: be:59:3f:b1:3b:c5:ee:b5:ea:0f:d6:98:45:ee:bb:4f:61:32: 23:f2:5a:b8:aa:a2:aa:9e:0a:fa:e0:da:45:0f:3e:a3:62:02: 30:50:a9:ff:7f:9c:e5:47:1f:e3:95:cb:c2:4c:27:86:75:be: 4f:23:9a:fa:5a:f1:3b:0e:59:1b:09:10:ca:cd:62:e2:38:70: 71:1c:00:22:06:e0:91:50:5c:c5:97:d7:06
Аргумент -text
означает, что мы хотим посмотреть представление сертификата в текстовом человекочитаемом виде, а -noout
означает, что собственно содержимое сертификата в виде PEM-блока на экран выводить не нужно.
──────────────────
Структура сертификата в виде ASN.1-определения задана в RFC 5280, раздел 4.1. Basic Certificate Fields:
Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING }
Если вы внимательно читали предыдущие разделы, то заметите, что структура аналогична CSR: блок данных типа TBSCertificate, идентификатор алгоритма и цифровая подпись. Вся содержательная часть сертификата находится внутри блока с типом TBSCertificate (напомню, что tbs расшифровывается как to be signed, то есть подлежит подписи или требует подписи). TBSCertificate определяется там же в RFC 5280 следующим образом:
TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version MUST be v3 }
Первые семь базовых полей являются обязательными, они всегда есть в любом сертификате (их семантика описана в RFC 5280, раздел 4.1.2 TBSCertificate):
- version
- версия формата сертификата, здесь может быть либо значение 0 (означает версию 1, указывается, когда в рассматриваемом блоке tbsCertificate есть только базовые поля); либо значение 2 (означает версию 3, указывается, когда есть дополнительные поля). Другие значения помимо 0 или 2 в стандарте не определены и на практике не должны использоваться.
- serialNumber
- порядковый номер сертификата в реестре УЦ, про него я писал выше, здесь должно быть число, уникальное для каждого сертификата, подписанного конкретным сертификатом УЦ.
- signature
- в этом поле указывается идентификатор алгоритма цифровой подписи, которую использовал удостоверяющий центр для подписи сертификата. Формат поля зависит от использованного алгоритма, но всегда включает в себя как минимум OID конкретного алгоритма.
- issuer
- здесь содержится имя сертификата УЦ, которым был подписан этот сертификат. Как и в случае с CSR, здесь записывается не строка, а строго структурированное значение distinguished name (DN), я о нём подробно писал в разделе Типы данных → Distinguished Name (DN).
- validity
- интервал, в течение которого сертификат может быть доверенным, состоит из двух значений: notBefore и notAfter.
- subject
- субъект сертификата, то есть та персона или организация, для которой он был выписан, формат поля — DN, такой же, как и для issuer. Также значение из этого поля принято считать названием или именем сертификата,именно оно используется для отображение в браузере, например.
- subjectPublicKeyInfo
- в этом поле записывается открытый ключ субъекта, он состоит из двух последовательных значений: идентификатора алгоритма (поле algorithm, см. Типы данных → AlgorithmIdentifier)) и собственно открытого ключа в бинарной форме (поле subjectPublicKey).
Расширения сертификата (Certificate extensions)¶
Когда openssl создаёт самоподписанный сертификат, то в него автоматически добавляются три расширения: Subject Key Identifier, Authority Key Identifier и Basic Constraints. В текстовом представлении сертификата расширения показываются в секции X509v3 extensions вот так:
X509v3 extensions: X509v3 Subject Key Identifier: 89:12:EA:73:BC:F2:E2:43:7F:E0:A4:54:A6:60:AE:B7:4D:EA:06:0C X509v3 Authority Key Identifier: keyid:89:12:EA:73:BC:F2:E2:43:7F:E0:A4:54:A6:60:AE:B7:4D:EA:06:0C X509v3 Basic Constraints: critical CA:TRUE
Я пока отмечу только расширение Basic Constraints, в нём указывается флаг-разрешение, может ли сертификат использоваться для подписывания других сертификатов. Если может, то подписанные им другие сертификаты корректным образом встраиваются в цепочку доверия. Если же разрешения нет, то подписанные сертификаты будут автоматически считаться недоверенными. Так как мы создаём сертификат УЦ, он автоматически помечается как разрешённый для подписывания других.
В расширениях обычно записывается дополнительная информация, которой не нашлось места в базовых полях.
Когда только X.509-сертификаты стали использоваться в SSL, название сайта (домен) записывалось в поле subject, в атрибуте CommonName (CN). Однако очень быстро стало понятно, что этого недостаточно, так как один и тот же сайт может иметь несколько доменных имён и для каждого нужен свой сертификат, поэтому было придумано расширение subjectAltName. В общем же случае расширение subjectAltName призвано отделить имя сертификата от identity, которая в сертификате хранится. Имя записывается в subject, identity — в subjectAltName. Полная спецификация этого расширения описана в RFC 5280, раздел 4.2.1.6 Subject Alternative Name.
Другой пример расширения — Basic Constraints, оно описано в RFC 5280, раздел 4.2.1.9 Basic Constraints. Через него удостоверяющий центр разрешает этому сертификату выступать в роли промежуточного (intermediate). Если такое разрешение прописано, то подписанные таким сертификатом другие сертификаты корректным образом встраиваются в цепочку доверия. Если же разрешения нет, то подписанные сертификаты будут автоматически считаться недоверенными.
У каждого расширения есть идентификатор — OID, например, для Subject Alternative Name это 2.5.29.17, а для Basic Constraints — 2.5.29.19. А формальное описание одного расширения на ASN.1 выглядит так:
Extension ::= SEQUENCE { extnID OBJECT IDENTIFIER, critical BOOLEAN DEFAULT FALSE, extnValue OCTET STRING }
В поле extnID записывается OID расширения. В булевском поле critical записывается специальный флаг критичности расширения, если он установлен и приложение не знает про такое расширение, то весь сертификат должен помечаться как недоверенный; если же флаг отсутствует, значение подразумевается false. В поле extnValue записывается байтовая строка, интерпретация которой зависит от конкретного расширения. Как правило, она закодирована в DER и для неё где-то существует соответствующее ASN.1 описание.
──────────────────
Вы можете указать нужные вам расширения при создании CSR, однако практически все удостоверяющие центры полностью игнорируют эти данные и заполняют их в сертификате самостоятельно.
Создание сертификата промежуточного УЦ¶
Сертификат корневого удостоверяющего центра практически никогда не используется для подписи конечных сертификатов заявителей, вместо этого им подписываются сертификаты промежуточных УЦ, а уже потом они используются для подписи конечных сертификатов.
Удостоверяющий центр называется промежуточным (intermediate), если его сертификат (который он использует для подписи) подписан каким-то другим сертификатом и при этом имеет разрешение на подписывание других сертификатов (через расширение Basic Constraints). Мы сделаем свой тестовый промежуточный УЦ, у него будет собственный ключ и собственный сертификат. Закрытый ключ на этот раз будет RSA 2048 бит и в формате PKCS#8:
[[email protected]]% openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:2048 -out demo-intermediate-ca.key ..+++++ ...........................................+++++
Дальше создаём CSR:
[[email protected]]% openssl req -new -batch -key demo-intermediate-ca.key \ -subj '/C=RU/L=Omsk/CN=Demo Intermediate Authority' -out demo-intermediate-ca.csr
Создаём файл demo-intermediate-ca-crt.cnf
с параметрами расширений, которые мы хотим включить в сертификат:
[cert_ext] authorityKeyIdentifier = keyid subjectKeyIdentifier = hash basicConstraints = critical, CA:true keyUsage = digitalSignature, keyCertSign
Вот что каждая строчка в нём означает:
- authorityKeyIdentifier = keyid
- Идентификатор ключа «родительского» удостоверяющего центра автоматически берётся из расширения Subject Key Identifier того сертификата, которым подписываем данный.
- subjectKeyIdentifier = hash
- Идентификатора собственного ключа порождается автоматически из открытого ключа по алгоритму из RFC 5280.
- basicConstraints = critical, CA:true
- Критичное расширение Basic Constraints, разрешающее выступать в роли удостоверяющего центра.
- keyUsage = digitalSignature, keyCertSign
- Разрешения, как можно использовать сертификата: цифровая подпись и подписывание других сертификатов.
И, наконец, создаём сертификат demo-intermediate-ca.crt
, используя конфигурационный файл:
[[email protected]]% openssl x509 -req -in demo-intermediate-ca.csr -days 365 \ -CA demo-root-ca.crt -CAkey demo-root-ca.key -CAcreateserial -extfile demo-intermediate-ca-crt.cnf \ -extensions cert_ext -out demo-intermediate-ca.crt
Пояснения по новым аргументам:
- -req
- указывает, что на вход в аргументе
-in regolit.csr
ожидается именно Certificate Signing Request; - -days 365
- выписываем сертификат на 365 дней;
- -CA demo-root-ca.crt -CAkey demo-root-ca.key
- указываем сертификат и закрытый ключ УЦ, из сертификата
demo-root-ca.pem
будет взято полеsubject
и вставлено в итоговой сертификат в полеissuer
; - -CAcreateserial
- это означает, что будет создан файл с именем
demo-root-ca.srl
, в который будет записан серийный номер сертификата, а дальше openssl из этого файла возьмёт значение и запишет в сертификат, про серийный номер я уже писал выше в теоретическом разделе, это должно быть число, уникальное для каждого сертификата, подписанного конкретным сертификатом УЦ. В реальном удостоверяющем центре ведётся реестр выписанных сертификатов и для каждого создаётся собственный уникальный serialNumber. Вы можете создать файлdemo-root-ca.srl
самостоятельно и прописать туда любое число. При каждом запуске команды создания сертификата на базе CSR значение в этом файле увеличивается на единицу. Если файла нет и аргумент-CAcreateserial
не указан, то openssl выдаст ошибку. Если файл уже существует, то аргумент-CAcreateserial
игнорируется. - -extfile demo-intermediate-ca-crt.cnf
- Путь к файлу с параметрами расширений.
- -extensions cert_ext
- Название секции в файле, где собственно и записаны параметры расширений.
──────────────────
К этом моменту у нас есть тестовый удостоверяющий центр, сертификатом которого (demo-intermediate-ca.crt
) мы можем подписывать сертификаты заявителей.
Создание сертификата из CSR¶
Несколькими разделами ранее мы создали CSR в файле regolit.csr
, также мы сделали цепочку из двух тестовых удостоверяющих центров: корневого и промежуточного. И теперь готовы подписать CSR-запрос сертификатом промежуточного УЦ demo-intermediate-ca.crt
. Последовательность действий здесь практически такая же, только будут прописаны другие расширения.
Удостоверяющий центр при создании сертификата на базе CSR заявителя обычно действует так:
- из CSR берутся: имя из поля subject и открытый ключ;
- из сертификата УЦ берётся значение поле subject, оно пойдёт в поле issuer сертификата;
- из другого источника берутся данные для расширений, например, список дополнительных доменов для расширения Subject Alternative Name, которые заявитель указал через форму на веб-сайте УЦ;
- из внутренней базы удостоверяющего центра берутся данные для других полей и расширений;
- на основе собранных данных собирается сертификат, подписывается закрытым ключом и отдаётся заявителю.
Создаём файл с параметрами расширений (regolit-crt.cnf
) со следующим содержимым:
[cert_ext] authorityKeyIdentifier = keyid basicConstraints = critical, CA:false subjectAltName = DNS:regolit.com, DNS:www.regolit.com keyUsage = digitalSignature
В нём заданы такие параметры:
- authorityKeyIdentifier = keyid
- Идентификатор ключа удостоверяющего центра автоматически берётся из расширения Subject Key Identifier того сертификата, которым подписываем данный, то есть из
demo-intermediate-ca.crt
. - basicConstraints = critical, CA:false
- УЦ явным образом запрещает создаваемый сертификат использовать для подписывания других сертификатов. Кроме того, значение помечено как критическое.
- subjectAltName = DNS:regolit.com, DNS:www.regolit.com
- Перечисляем все дополнительные записи-identity, которые мы хотим добавить в сертификат
- keyUsage = digitalSignature
- Разрешаем использование сертификата для проверки цифровой подписи.
И вот итоговая команда:
[[email protected]]% openssl x509 -req -in regolit.csr -days 365 -CA demo-intermediate-ca.crt \ -CAkey demo-intermediate-ca.key -CAcreateserial -extfile regolit-crt.cnf -extensions cert_ext -out regolit.crt
Получаем вот такой сертификат с расширениями:
[[email protected]]% openssl x509 -in regolit.crt -noout -text Certificate: Data: Version: 3 (0x2) Serial Number: 5a:25:eb:62:a4:84:9f:75:42:9a:6e:d0:ac:aa:0b:d8:eb:34:26:d4 Signature Algorithm: sha256WithRSAEncryption Issuer: C = RU, L = Omsk, CN = Demo Intermediate Authority Validity Not Before: Sep 1 10:06:40 2020 GMT Not After : Sep 1 10:06:40 2021 GMT Subject: C = RU, ST = NSO, L = Novosibirsk, O = Regolit, CN = test.regolit.com, emailAddress = [email protected] Subject Public Key Info: Public Key Algorithm: id-ecPublicKey Public-Key: (256 bit) pub: 04:16:82:ea:92:e6:3a:04:62:0a:16:b4:04:ec:1d: ...skipped... 50:50:63:d6:70 ASN1 OID: prime256v1 NIST CURVE: P-256 X509v3 extensions: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Alternative Name: DNS:regolit.com, DNS:www.regolit.com X509v3 Key Usage: Digital Signature Signature Algorithm: sha256WithRSAEncryption 3b:2a:fd:fc:0d:9a:ae:9a:d5:83:d8:74:28:a1:87:6a:58:ab: ...skipped... 1f:8c:70:29
Тестирование сертификатов¶
Для проверки сертификатов можно воспользоваться командами openssl s_server
и openssl s_client
. Первая запускает простейший TLS веб-сервер с указанными сертификатами и на указанном адресе. А вторая обращается к любому указанному адресу и верифицирует соединение.
Важное свойство этих команд в том, что вы полностью контролируете формирование цепочки доверия. Например, вы можете указать для openssl s_client
файл с доверенными сертификатами и тогда при установке соединения цепочки будут строиться только с использованием указанных сертификатов, а не всех системных. Аналогично с openssl s_server
— вы можете указать, какие промежуточные сертификаты передавать клиентами при установке соединения.
Для дальнейших действий нам понадобится два окна с терминалами, в каждом должен быть открыт каталог со всеми файлами из предыдущих частей статьи: сертификатами и ключами удостоверяющих центров и заявителя.
Сначала выступаем в роли заявителя, который хочет запустить веб-сервер с только что полученным сертификатом. У заявителя есть сертификат — файл regolit.crt
, закрытый ключ — prime256v1-eckey.pem
, а также сертификат удостоверяющего центра — demo-intermediate-ca.crt
. При этом предполагается, что в браузере клиента в списке доверенных сертификатов есть корневой сертификат demo-root-ca.crt
, но при этом там нет промежуточного сертификата demo-intermediate-ca.crt
.
В первом терминале запускаем такую команду для старта TLS веб-сервера:
[[email protected]]% openssl s_server -accept 127.0.0.1:9443 -key prime256v1-eckey.pem -cert regolit.crt -cert_chain demo-intermediate-ca.crt Using default temp DH parameters ACCEPT
Если всё сделано правильно и в каталоге лежат нужные файлы, программа запустит веб-сервер по адресу https://127.0.0.1:9443, вы даже можете открыть его в браузере, но он сразу выдаст вам ошибку вида NET::ERR_CERT_AUTHORITY_INVALID
.
Теперь запустим в другом терминале команду для проверки соединения (указываем аргументы -no-CApath
, чтобы не использовать системное хранилище доверенных сертификатов и -quiet
, чтобы не показывать подробную информацию о соединении, в аргументе -servername www.regolit.com
указываем название домена, которое клиент передаёт при инициализации соединения в расширении протокола TLS Server Name Indication / SNI 1):
[[email protected]]% openssl s_client -connect 127.0.0.1:9443 -no-CApath -CAfile demo-root-ca.crt -servername www.regolit.com -quiet depth=2 C = AQ, O = Penguin Co., CN = Demo Root CA verify return:1 depth=1 C = RU, L = Omsk, CN = Demo Intermediate Authority verify return:1 depth=0 C = RU, ST = NSO, L = Novosibirsk, O = Regolit, CN = test.regolit.com, emailAddress = [email protected] verify return:1
Программа выдаёт поле subject каждого из сертификатов в построенной цепочке, в данном случае она успешно построена и проверифицирована, так как сообщений об ошибке нет. Сертификат сервера показывается в самом низу. Программа ждёт от вас ввода HTTP-запроса, но мы пока это пропустим и просто нажмём Ctrl+C, чтобы завершить команду.
Если мы в команде вместо аргумента -CAfile demo-root-ca.crt
укажем -no-CAfile
(означает, что у клиента нет доверенного корневого сертификата), то получим такое:
[[email protected]]% openssl s_client -connect 127.0.0.1:9443 -no-CApath -no-CAfile -servername www.regolit.com -quiet depth=1 C = RU, L = Omsk, CN = Demo Intermediate Authority verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C = RU, ST = NSO, L = Novosibirsk, O = Regolit, CN = test.regolit.com, emailAddress = [email protected] verify return:1
Так как доверенного сертификата у клиента нет, он не может построить корректную цепочку доверия, поэтому выдаёт ошибку verify error:num=20:unable to get local issuer certificate
. Однако по-прежнему показывает, что от сервера пришло два сертификата. Снова завершаем программу по Ctrl+C.
──────────────────
Следующий сценарий: сервер при соединии отдаёт только сертификат домена без промежуточного (убираем аргумент -cert_chain
):
[[email protected]]% openssl s_server -accept 127.0.0.1:9443 -key prime256v1-eckey.pem -cert regolit.crt Using default temp DH parameters ACCEPT
Если мы теперь попытаемся подключиться клиентом, то увидим сразу две ошибки верификации сертификата:
[[email protected]]% openssl s_client -connect 127.0.0.1:9443 -no-CApath -CAfile demo-root-ca.crt -servername www.regolit.com -quiet depth=0 C = RU, ST = NSO, L = Novosibirsk, O = Regolit, CN = test.regolit.com, emailAddress = [email protected] verify error:num=20:unable to get local issuer certificate verify return:1 depth=0 C = RU, ST = NSO, L = Novosibirsk, O = Regolit, CN = test.regolit.com, emailAddress = [email protected] verify error:num=21:unable to verify the first certificate verify return:1
- verify error:num=20:unable to get local issuer certificate
- Эта ошибка означает, что клиент не смог найти сертификат, которым подписан сертификат домена — сервер промежуточный сертификат в запросе не передал. Буквальный текст описания этой ошибки из openssl: the issuer certificate could not be found: this occurs if the issuer certificate of an untrusted certificate cannot be found.
- verify error:num=21:unable to verify the first certificate
- Клиент не смог создать верифицированную цепочку доверия, включающую сертификат сайта. Буквальный текст описания этой ошибки из openssl: no signatures could be verified because the chain contains only one certificate and it is not self signed.
──────────────────
Если вы хотите протестировать на «живом» окружении, можете импортировать корневой сертификат demo-root-ca.crt
в системное хранилище сертификатов и пометить его как доверенный. Тогда вы сможете открыть тестовый URL в обычном браузере.
Верификация цепочки сертификатов¶
Для верификации сертификата вам нужна цепочка доверия, завершающаяся корневым сертификатом. У нас уже есть все нужные для этого файлы и мы можем записать сертфикаты удостоверяющих центров в один файл и затем использовать его для верификации при помощи команды openssl verify
:
[[email protected]]% cat demo-intermediate-ca.crt demo-root-ca.crt > chain.pem [[email protected]]% openssl verify -no-CApath -CAfile chain.pem regolit.crt regolit.crt: OK
Здесь мы сначала записываем оба наших сертификата УЦ в один файл chain.pem
и дальше используе его для верификации.
Если мы не хотим строить целиком цепочку, а хотим только проверить, подписан ли один сертификат другим, нужно добавить аргумент -partial_chain
:
[[email protected]]% openssl verify -no-CApath -CAfile demo-intermediate-ca.crt -partial_chain regolit.crt regolit.crt: OK
Здесь мы указали в качестве «цепочки» только один — промежуточный — сертификат удостоверяющего центра demo-intermediate-ca.crt
, и без аргумента -partial_chain
мы бы получили ошибку верификации:
[[email protected]]% openssl verify -no-CApath -CAfile demo-intermediate-ca.crt regolit.crt C = RU, L = Omsk, CN = Demo Intermediate Authority error 2 at 1 depth lookup: unable to get issuer certificate error regolit.crt: verification failed
Использование сертификатов для цифровой подписи любых файлов¶
От одного из предыдущих этапов у нас остался сертификат в файле regolit.crt
и закрытый ключ для него — prime256v1-eckey.pem
, мы им подпишем файл demo-intermediate-ca.crt
:
[[email protected]]% openssl dgst -sha256 -sign prime256v1-eckey.pem -out demo-intermediate-ca.crt.sig demo-intermediate-ca.crt
Здесь мы используем команду openssl dgst
, основное назначение которой — подсчёт дайджеста файла (то есть криптографического хеша). Однако эта же команда умеет генерировать и верифицировать цифровые подписи. Для генерации мы передаём закрытый ключ в аргументе -sign prime256v1-eckey.pem
.
В openssl на текущий момент (5 сентября 2020 года) не существует штатной команды для проверки цифровой подписи при помощи сертификата, вам придётся вручную отдельно выделить открытый ключ из сертификата и дальше уже им верифицировать.
Выделяем открытый ключ из сертификата:
[[email protected]]% openssl x509 -in regolit.crt -pubkey -noout > regolit-pubkey.pem
И верифицируем файл demo-intermediate-ca.crt
, используя цифровую подпись и выделенный только что открытый ключ:
[[email protected]]% openssl dgst -sha256 -verify regolit-pubkey.pem -signature demo-intermediate-ca.crt.sig demo-intermediate-ca.crt Verified OK
Проверка соответствия сертификата и закрытого ключа¶
Есть два файла: сертификат (cert.pem
) и закрытый ключ (key.pem
), нужно проверить, соответствует ли открытый ключ из сертификата этому закрытому ключу. Обычно этот вопрос решают через экспорт открытого ключа из сертификата и закрытого ключа, и последующего их сравнения, однако этот метод для разных алгоритмов использует очень разные команды, поэтому я буду пользоваться способом из предыдущего раздела — через цифровую подпись.
Алгоритм такой:
-
Сделаем цифровую подпись файла
cert.pem
, используя закрытый ключ.[[email protected]]% openssl dgst -sha256 -sign key.pem -out test.sig cert.pem
-
Выделим открытый ключ из сертификата и запишем его в файл
cert-pubkey.pem
:[[email protected]]% openssl x509 -in cert.pem -pubkey -noout -out cert-pubkey.pem
-
Проверим цифровую подпись:
[[email protected]]% openssl dgst -sha256 -verify cert-pubkey.pem -signature test.sig cert.pem Verified OK
Использование сертификатов для шифрования¶
Этот раздел довольно экстремальный, поскольку описанный в нём метод мало подходит для практического применения. Однако он описывает подход, применяемый в современном TLS-шифровании веб-трафика.
В общем случае X.509-сертификаты не предназначены для шифрования данных, единственный пригодный для этого алгоритм — RSA, однако это исключение. Тем не менее, в разделе Используемые криптоалгоритмы я рассказал, как можно использовать сертификаты совместно с протоколом Diffie-Hellman для безопасного обмена ключами симметричного шифрования. А в этом разделе я расскажу, как это можно реализовать на практике через команды openssl.
Действующие лица: Алиса и Боб, у каждого из них есть свой X.509-сертификат с закрытым ключом, причём Алиса и Боб заранее обменялись сертификатами каким-то надёжным способом (например, при личной встрече). И с этого момента весь обмен данными происходит через небезопасный открытый канал.
Алиса хочет организовать безопасный сеанс связи с Бобом поверх небезопасного канала. Для этого отлично подходит симметричный алгоритм AES-128-CBC, однако для него обе стороны должны использовать одинаковый ключ — набор байтов размером 128 бит. Алиса и Боб будут для обмена ключом пользоваться протоколом Diffie-Hellman с аутентификацией через цифровые подписи.
Создадим сначала сертификаты и закрытые ключи к ним для каждой из сторон. Алиса создаёт закрытый ключ alice-x509.key
и самоподписанный сертификат alice-x509.crt
:
[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout -out alice-x509.key [[email protected]]% openssl req -new -x509 -batch -days 5000 -key alice-x509.key -subj '/CN=Alice' -out alice-x509.crt
И Боб делает то же самое — ключ в файле bob-x509.key
и самоподписанный сертификат bob-x509.crt
:
[[email protected]]% openssl ecparam -name prime256v1 -genkey -noout -out bob-x509.key [[email protected]]% openssl req -new -x509 -batch -days 5000 -key bob-x509.key -subj '/CN=Bob' -out bob-x509.crt
Дальше стороны должны заранее надёжным и безопасным способом передать друг-другу свои сертификаты.
──────────────────
Через какое-то время Алиса захотела обменяться важными данными с Бобом через e-mail. Это начало новой сессии обмена, в которой будут использоваться новые ключи (эфемерные, то есть не фиксированные, а созданные специально для этого момента, после сессии эфемерные ключи стираются), поэтому Алиса должна выбрать параметры протокола Diffie-Hellman, это делается такой командой:
[[email protected]]% openssl dhparam -dsaparam -out dhp.pem 4096 Generating DSA parameters, 4096 bit long prime ...+.................+
Здесь 4096 означает размер случайного простого числа в битах, он в настоящее время считается безопасным. Теперь Алиса на основе этого файла с параметрами создаёт свой закрытый ключ dh-alice.key
(это ещё не эфемерный ключ, а только заготовка для него!):
[[email protected]]% openssl genpkey -paramfile dhp.pem -out alice-dh.key
И выделяет из него открытый ключ:
[[email protected]]% openssl pkey -in alice-dh.key -pubout -out alice-dh-pub.pem
Затем Алиса конкатенирует два файла dhp.pem
и alice-dh-pub.pem
в один alice2bob.pem
:
[[email protected]]% cat dhp.pem alice-dh-pub.pem > alice2bob.pem
И создаёт цифровую подпись для него (в виде отдельного файла alice2bob.pem.sig
), используя свой закрытый ключ от X.509-сертификата:
[[email protected]]% openssl dgst -sha256 -sign alice-x509.key -out alice2bob.pem.sig alice2bob.pem
Дальше Алиса отправляет Бобу два файла: alice2bob.pem
и alice2bob.pem.sig
.
──────────────────
Боб получает от Алисы файл и цифровую подпись. Для проверки подписи ему сначала нужно извлечь из сертификата Алисы открытый ключ в файл alice-x509-pubkey.pem
:
[[email protected]]% openssl x509 -in alice-x509.crt -pubkey -noout > alice-x509-pubkey.pem
И дальше проверить им цифровую подпись:
[[email protected]]% openssl dgst -sha256 -verify alice-x509-pubkey.pem -signature alice2bob.pem.sig alice2bob.pem Verified OK
Проверка прошла успешно, теперь Боб уверен, что полученный им файл alice2bob.pem
действительно пришёл от Алисы, поэтому можно продолжать. Боб создаёт свой закрытый DH-ключ на основе полученных параметров:
[[email protected]]% openssl genpkey -paramfile alice2bob.pem -out bob-dh.key
Обратите внимание, что в аргументе с параметрами указан файл alice2bob.pem
, в котором записано два разных PEM-блока, но так как PEM — это контейнер, openssl берёт из него только те данные, которые ему нужны в текущем контексте, а здесь контекст — это параметры DH, именно такой блок и будет прочитан. Другими словами, нет никакой необходимости разделять файл на части.
Дальше Боб выделяет открытый ключ на основе только что созданного закрытого:
[[email protected]]% openssl pkey -in bob-dh.key -pubout -out bob-dh-pub.pem
И, наконец, создаёт свой эфемерный закрытый ключ в файле bob-secret.key
:
[[email protected]]% openssl pkeyutl -derive -inkey bob-dh.key -peerkey alice2bob.pem -out bob-secret.key
Теперь Боб создаёт цифровую подпись для своего открытого DH-ключа:
[[email protected]]% openssl dgst -sha256 -sign bob-x509.key -out bob-dh-pub.pem.sig bob-dh-pub.pem
И отправляет Алисе два файла: bob-dh-pub.pem
и bob-dh-pub.pem.sig
.
──────────────────
Алиса проверяет цифровую подпись аналогичным образом: выделяет открытый ключ из сертификата Боба и верифицирует файл bob-dh-pub.pem
с подписью bob-dh-pub.pem.sig
:
[[email protected]]% openssl x509 -in bob-x509.crt -pubkey -noout > bob-x509-pubkey.pem [[email protected]]% openssl dgst -sha256 -verify bob-x509-pubkey.pem -signature bob-dh-pub.pem.sig bob-dh-pub.pem Verified OK
И, наконец, Алиса создаёт свой эфемерный ключ на основе собственного закрытого DH-ключа (alice-dh.key
) и полученного от Боба открытого DH-ключа (bob-dh-pub.pem
):
[[email protected]]% openssl pkeyutl -derive -inkey alice-dh.key -peerkey bob-dh-pub.pem -out alice-secret.key
Мы можем убедиться, что оба их эфемерных ключа одинаковые:
[[email protected]]% diff -s alice-secret.key bob-secret.key Files alice-secret.key and bob-secret.key are identical
──────────────────
Теперь Алиса может зашифровать файл alice-message.txt
(внутри которого только одна строчка: Very secret message.), используя эфемерный ключ в качестве пароля:
[[email protected]]% openssl enc -e -md md5 -pass file:alice-secret.key -aes-128-cbc \ -in alice-message.txt -out alice-message.txt.encrypted
Здесь мы используем алгоритм хеширования MD5, чтобы получить из файла эфемерного ключа ровно 128 бит AES-ключа.
Боб теперь может расшифровать файл так:
[[email protected]]% openssl enc -d -md md5 -pass file:bob-secret.key -aes-128-cbc -in alice-message.txt.encrypted Very secret message.
Аналогичным образом Боб может зашифровать свой текст и отправить Алисе, ключ для его расшифровки есть только у них двоих. После завершения обмена сообщениями они оба удаляют свои эфемерные ключи и для следующего сеанса создают их заново.
Несколько примеров необычных сертификатов¶
Возьмём вот этот сертификат, это корневой сертификат Казахстанского центра межбанковских расчётов национального банка Республики Казахстан.
Вот как выглядит попытка распечатать его в текстовом виде:
Certificate: Data: Version: 3 (0x2) Serial Number: 1e:97:16:12:b3:4f:8d:e4:e8:39:8b:da:34:f5:1e:f5:3f:c6:0f:b8:29:cf:7a:07:c0:7a:db:f5:9f:e9:12:0b Signature Algorithm: 1.3.6.1.4.1.6801.1.2.2 Issuer: CN=KISC Root CA, O=KISC, C=KZ Validity Not Before: Sep 2 12:28:57 2008 GMT Not After : Aug 28 12:28:57 2028 GMT Subject: CN=KISC Root CA, O=KISC, C=KZ Subject Public Key Info: Public Key Algorithm: 1.3.6.1.4.1.6801.1.5.8 Unable to load Public Key 4594828908:error:06FFF09C:digital envelope routines:CRYPTO_internal:unsupported algorithm:/p_lib.c:241: 4594828908:error:0BFFF06F:x509 certificate routines:CRYPTO_internal:unsupported algorithm:x_pubkey.c:199: X509v3 extensions: X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Key Usage: critical Certificate Sign, CRL Sign X509v3 Subject Key Identifier: 1E:97:16:12:B3:4F:8D:E4:E8:39:8B:DA:34:F5:1E:F5:3F:C6:0F:B8:29:CF:7A:07:C0:7A:DB:F5:9F:E9:12:0B X509v3 Authority Key Identifier: keyid:1E:97:16:12:B3:4F:8D:E4:E8:39:8B:DA:34:F5:1E:F5:3F:C6:0F:B8:29:CF:7A:07:C0:7A:DB:F5:9F:E9:12:0B DirName:/CN=KISC Root CA/O=KISC/C=KZ serial:1E:97:16:12:B3:4F:8D:E4:E8:39:8B:DA:34:F5:1E:F5:3F:C6:0F:B8:29:CF:7A:07:C0:7A:DB:F5:9F:E9:12:0B Signature Algorithm: 1.3.6.1.4.1.6801.1.2.2 b4:dc:79:0f:a7:94:8f:fa:90:22:18:f9:22:27:30:83:33:59: af:b9:68:6b:1d:40:75:ad:87:e0:ff:46:37:3c:0a:78:55:b4: c3:b1:1a:8f:6c:62:37:ad:38:1b:9c:b6:1c:ac:68:16:37:c1: 8e:ae:6e:9c:7a:c4:00:6d:ff:3a
Сразу же видим, что openssl не знает ни алгоритма открытого ключа (OID 1.3.6.1.4.1.6801.1.5.8), ни алгоритма цифровой подписи (OID 1.3.6.1.4.1.6801.1.2.2). Репозиторий oid-info.com тоже не знает про них ничего, однако знает google — это идентификаторы объектов из проприетарного ПО TumarCSP, которое используется в Казахстане. По факту это другие идентификаторы для объектов ГОСТовских криптоалгоритмов.
──────────────────
Следующий файл для препарирования — старый сертификат удостоверяющего центра Бурятии (ca-bur.der). Его текстовое представление великолепно:
Certificate: Data: Version: 3 (0x2) Serial Number: 1e:5d:f6:44:00:00:00:00:02:51 Signature Algorithm: GOST R 34.11-94 with GOST R 34.10-2001 Issuer: INN = 007710474375, OGRN = 1047702026701, emailAddress = [email protected], street = 125375 \D0\B3. \D0\9C\D0\BE\D1\81\D0\BA\D0\B2\D0\B0 \D1\83\D0\BB. \D0\A2\D0\B2\D0 \B5\D1\80\D1\81\D0\BA\D0\B0\D1\8F \D0\B4.7, O = \D0\9C\D0\B8\D0\BD\D0\BA\D0\BE\D0\BC\D1\81 \D0\B2\D1\8F\D0\B7\D1\8C \D0\A0\D0\BE\D1\81\D1\81\D0\B8\D0\B8, L = \D0\9C\D0\BE\D1\81\D0\BA \D0\B2\D0\B0, ST = 77 \D0\B3.\D0\9C\D0\BE\D1\81\D0\BA\D0\B2\D0\B0, C = RU, CN = \D0\A3\D0\A6 2 \D0\98\D0\A1 \D0\93\D0\A3\D0\A6 Validity Not Before: Jun 17 06:35:00 2014 GMT Not After : Jul 22 06:54:00 2017 GMT Subject: OGRN = 1020300972361, INN = 000323082280, street = \D1\83\D0\BB. \D0\9B\D0\B5 \D0\BD\D0\B8\D0\BD\D0\B0 54, emailAddress = [email protected], C = RU, ST = 03 \D0\A0\D0\B5\D1 \81\D0\BF\D1\83\D0\B1\D0\BB\D0\B8\D0\BA\D0\B0 \D0\91\D1\83\D1\80\D1\8F\D1\82\D0\B8\D1\8F, L = \D0\A3\D0\BB\D0\B0\D0\BD-\D0\A3\D0\B4\D1\8D, O = \D0\90\D0\B4\D0\BC\D0\B8\D0\BD\D0\B8\D1\81\D1\82 \D1\80\D0\B0\D1\86\D0\B8\D1\8F \D0\93\D0\BB\D0\B0\D0\B2\D1\8B \D0\A0\D0\91 \D0\B8 \D0\9F\D1\80\D0 \B0\D0\B2\D0\B8\D1\82\D0\B5\D0\BB\D1\8C\D1\81\D1\82\D0\B2\D0\B0 \D0\A0\D0\91, CN = \D0\A3\D0\A6 \D0 \A0\D0\B5\D1\81\D0\BF\D1\83\D0\B1\D0\BB\D0\B8\D0\BA\D0\B8 \D0\91\D1\83\D1\80\D1\8F\D1\82\D0\B8\D1\8F Subject Public Key Info: Public Key Algorithm: GOST R 34.10-2001 Unable to load Public Key 140269877257344:error:0609E09C:digital envelope routines:pkey_set_type:unsupported algorithm:../crypto/evp/p_lib.c:210: 140269877257344:error:0B09406F:x509 certificate routines:x509_pubkey_decode:unsupported algorithm:../crypto/x509/x_pubkey.c:114: X509v3 extensions: X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: 55:D2:55:96:82:D3:B1:E3:62:EC:60:34:BE:D3:42:19:C3:45:B5:74 X509v3 Key Usage: Digital Signature, Certificate Sign, CRL Sign 1.3.6.1.4.1.311.21.1: ..... X509v3 Certificate Policies: Policy: 1.2.643.100.113.1 Policy: 1.2.643.100.113.2 Policy: X509v3 Any Policy Signing Tool of Subject: .-".................. CSP" (............ 3.6.1) X509v3 Authority Key Identifier: keyid:C6:6B:C1:02:A2:92:AA:14:0A:0A:4A:14:FD:19:1D:0D:57:D0:44:9C DirName:/[email protected]/C=RU/ST=77 \xD0\xB3. \xD0\x9C\xD0\xBE\xD1 \x81\xD0\xBA\xD0\xB2\xD0\xB0/L=\xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0 serial:3C:82:25:19:00:00:00:00:00:18 X509v3 CRL Distribution Points: Full Name: URI:http://rostelecom.ru/cdp/vguc2.crl Full Name: URI:http://reestr-pki.ru/cdp/vguc2.crl Authority Information Access: CA Issuers - URI:http://rostelecom.ru/cdp/vguc2.crt CA Issuers - URI:http://reestr-pki.ru/cdp/vguc2.crt X509v3 Private Key Usage Period: Not Before: Jun 17 06:35:00 2014 GMT, Not After: Jun 17 06:35:00 2018 GMT Signing Tool of Issuer: 0...-".................. CSP" (............ 3.6.1).S"............................ .......... ".................. ...." ............ 1.5.%... ..../124-2239 .... 04.10.2013 ....%... ..../128-1823 .... 01.06.2012 ... Signature Algorithm: GOST R 34.11-94 with GOST R 34.10-2001 3c:eb:c0:76:c9:3a:71:89:1a:b4:66:00:7f:ab:4d:ef:06:d8: bf:df:f7:23:d8:41:56:8e:65:af:5a:59:f9:f9:83:7a:d4:56: c5:53:26:44:bf:c8:46:e8:18:e9:ca:7d:74:4d:83:50:f5:d9: ac:0c:3b:2b:b1:ac:17:2b:9c:41
Здесь мы сразу видим, что openssl крайне плохо справляется с отображением сертификатов, содержащих юникодные символы, в нашем случае это кириллица в полях issuer и subject.
Отображение кириллицы частично можно исправить, добавив аргумент -nameopt sep_multiline,utf8
:
[[email protected]]% openssl x509 -inform DER -noout -text -in ca-bur.der -nameopt sep_multiline,utf8 Certificate: Data: Version: 3 (0x2) Serial Number: 1e:5d:f6:44:00:00:00:00:02:51 Signature Algorithm: GOST R 34.11-94 with GOST R 34.10-2001 Issuer: INN=007710474375 OGRN=1047702026701 [email protected] street=125375 г. Москва ул. Тверская д.7 O=Минкомсвязь России L=Москва ST=77 г.Москва C=RU CN=УЦ 2 ИС ГУЦ Validity Not Before: Jun 17 06:35:00 2014 GMT Not After : Jul 22 06:54:00 2017 GMT Subject: OGRN=1020300972361 INN=000323082280 street=ул. Ленина 54 [email protected] C=RU ST=03 Республика Бурятия L=Улан-Удэ O=Администрация Главы РБ и Правительства РБ CN=УЦ Республики Бурятия Subject Public Key Info: Public Key Algorithm: GOST R 34.10-2001 Unable to load Public Key 140283884340352:error:0609E09C:digital envelope routines:pkey_set_type:unsupported algorithm:../crypto/evp/p_lib.c:210: 140283884340352:error:0B09406F:x509 certificate routines:x509_pubkey_decode:unsupported algorithm:../crypto/x509/x_pubkey.c:114: X509v3 extensions: X509v3 Basic Constraints: critical CA:TRUE, pathlen:0 X509v3 Subject Key Identifier: 55:D2:55:96:82:D3:B1:E3:62:EC:60:34:BE:D3:42:19:C3:45:B5:74 X509v3 Key Usage: Digital Signature, Certificate Sign, CRL Sign 1.3.6.1.4.1.311.21.1: ..... X509v3 Certificate Policies: Policy: 1.2.643.100.113.1 Policy: 1.2.643.100.113.2 Policy: X509v3 Any Policy Signing Tool of Subject: .-".................. CSP" (............ 3.6.1) X509v3 Authority Key Identifier: keyid:C6:6B:C1:02:A2:92:AA:14:0A:0A:4A:14:FD:19:1D:0D:57:D0:44:9C DirName:/[email protected]/C=RU/ST=77 \xD0\xB3. \xD0\x9C\xD0\xBE\xD1\x81\xD0\xBA\xD0\xB2\xD0\xB0/L=\xD0\x9C\xD0\xBE\xD1\x81 \xD0\xBA\xD0\xB2\xD0\xB0 serial:3C:82:25:19:00:00:00:00:00:18 X509v3 CRL Distribution Points: Full Name: URI:http://rostelecom.ru/cdp/vguc2.crl Full Name: URI:http://reestr-pki.ru/cdp/vguc2.crl Authority Information Access: CA Issuers - URI:http://rostelecom.ru/cdp/vguc2.crt CA Issuers - URI:http://reestr-pki.ru/cdp/vguc2.crt X509v3 Private Key Usage Period: Not Before: Jun 17 06:35:00 2014 GMT, Not After: Jun 17 06:35:00 2018 GMT Signing Tool of Issuer: 0...-".................. CSP" (............ 3.6.1).S"............................ .......... ".................. ...." ............ 1.5.%... ..../124-2239 .... 04.10.2013 ....%... ..../128-1823 .... 01.06.2012 ... Signature Algorithm: GOST R 34.11-94 with GOST R 34.10-2001 3c:eb:c0:76:c9:3a:71:89:1a:b4:66:00:7f:ab:4d:ef:06:d8: bf:df:f7:23:d8:41:56:8e:65:af:5a:59:f9:f9:83:7a:d4:56: c5:53:26:44:bf:c8:46:e8:18:e9:ca:7d:74:4d:83:50:f5:d9: ac:0c:3b:2b:b1:ac:17:2b:9c:41
Однако данные внутри расширений по-прежнему отображаются в закодированном виде.
В качестве алгоритма открытого ключа используется GOST R 34.10-2001, а для цифровой подписи — хеш-функция GOST R 34.11-94 с алгоритмом цифровой подписи GOST R 34.10-2001
Примечания¶
-
Расширение протокола TLS Server Name Indication (сокращённо SNI) позволяет при инициализации TLS-соединения передать серверу домен веб-сайта, чтобы сервер выбрал подходящий сертификат именно для этого сайта. Без такого расширения сервер всегда отдавал одинаковый сертификат при соединеннии к конкретному IP-адресу и если на нём было несколько разных HTTPS-сайтов, начинались проблемы с сертификатом. ↩
"Протокол DH, очевидно, уязвим для атак типа Man-in-the-middle..." непонятно где была взята вами формула для этого алгоритма.Оригинальная фромула A = gX mod p и B = gY
Протокол DH не уязвим для MiTM при достаточно длином ключе
Я прямым текстом написал: Я выбрал операцию сложения ради простоты объяснения, в реальном протоколе используется сложно-обратимая операция
Для предотвращения MITM как раз используются цифровые подписи, без подобного слоя DH уязвим к MITM.