Expertus metuit
Человеческим языком о цифровых сертификатах: ASN.1, X.509, PKI
2016-01-13 10:52
Теги: openssl, crypto

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

Цифровые (электронные) сертификаты используются повсеместно, самая известная область их применения — это шифрование HTTPS-трафика в браузере. В таком виде с сертификатами знакомы все, однако более-менее адекватного понимающих гораздо меньше. Многие статьи на эту тему с самого начала топят читателя в абстрактных и несущественных деталях (например, раз, два, три). Между тем, базовые концепции системы сертификатов очень простые и я о них расскажу в этой статье. Эти концепции в моём изложении не являются абсолютно точными, но зато они помогут «въехать» в тему.

Традиционный дисклеймер: все примеры ориентированы на линуксовую, юниксовую или макосную консоль. Если хотите самостоятельно повторить куски консольного кода, поставьте openssl, например, через sudo apt-get install openssl

Зачем нужны сертификаты

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

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

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

Но тут возникает вопрос: а где гарантия, что этот сертификат (и соответственно публичный ключ) настоящий, что он действительно создан автором сайта, а не вклинившимся в сеть злоумышленником? Как проверить подлинность этого «удостоверения сайта»?

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

Если для SSH схема с заранее сохранённым сертификатом более-менее работает, то для HTTPS в браузере она уже не годится, абсолютно невозможно получить сертификаты для всех сайтов в интернете. Поэтому в HTTPS используется другая схема, называемая инфраструктурой публичных ключей или по-английски Public Key Infrastructure, сокращённо PKI.

Суть PKI очень простая: в браузере хранятся не сертификаты браузеров, а сертификаты так называемых удостоверяющих центров (по-английски Certificate Authority, сокращённо CA). Предназначение CA — это подписывание всех остальных сертификатов, это означает, что когда ваш браузер получает сертификат сайта при установке соединения, он видит помимо адреса сайта ещё и «адрес» удостоверяющего центра, а также цифровую подпись, которую сгенерировал удостоверяющий центр с использованием своего секретного ключа. Дальше браузер берёт из локального хранилища сертификат удостоверяющего центра, достаёт из него публичный ключ и с помощью него проверяет подпись в сертификате сайта. Если подпись правильная, соединение успешно устанавливается.

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

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

Технические подробности

А теперь самое главное, ради чего эта статья и писалась — технические подробности, стандарты и особенности реализации системы сертификатов.

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

Секретный ключ

Главный компонент всего — криптография, точнее криптографические алгоритмы.

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

[user@shell]$ openssl genrsa 2048
Generating RSA private key, 2048 bit long modulus
..........................................+++
......................+++
e is 65537 (0x10001)
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtqYUwQ3QwKf8l9St1IK2jNyZiL7PDVKdaWo8BoY0a7zZLyST
a5lS09k0Jcdsh3pV0X9PkNb1x+m4YGETjNfZUsd2O1/IwP1Fj+w3y75cy/ON4/so
6oxrUrIYwCbu9i2IxvevQJpClmn105QbIF4d+Pp5pMSfivngNl/BGLm4O5uyqhfn
LaouOCOroY9b77vNVHBkbq+eQjZBwvIFz1DzVfGVXsS1oCt36nTI38txHGTaCqgi
cxznqj7GBiaJi1b7jCvWLUoZi64AGCK4gWFkijNLuDsnR4h0WUJ90wge9Xt+srii
/iElvo2P1U9feL1znj4PZumZNJ3BzYU3qNWJ8wIDAQABAoIBAQCzb3X0Mx5iJqaA
gvBDVicBO7eaH9pJvF/or/VIc5AMR/sV1Vj+3CIC/d+9Pa3has3kgq4oHQZY38PC
65vJQkS+jjYZHoCbGDa+rdIi12FS/HLpBlWsF0dYdp7aJ2WbdCBrV+lUDjhcjLx0
n4wGwG+xqmDW/lO+tL0QrgGFyO61nxdEsltgvjlGFhUu4ugskwOvL3QoWv4SghHm
6oIyDhlGo3W6WpGV+Xt3/IR5bWFF0bn5bt9arHDiow1Vg0MeBxB+iuMYuctlXHaJ
O5UMs/BgqidV2m4wjoQxWg41tdeKEWmetWOm7LVZinmC7EIugug7xKrGVGAAexaj
fENVOxgBAoGBAN/9sB9BSZsA+C9DYtbKHTEO5IY+nBYGzxigyj8JlqwsibpN9dyZ
ZXML/UvgspYbKS8nafip/GQpdxIPeAhqKJSI3dVeml6MxKTwJj7HCr+0iuXg5m9d
AwLBLxHi+6hEN7TJb7JONB07QB9y78eAk94y9q4exAYh3Pnzn1lZR9JJAoGBANC/
9iiMFagQrvCkcKCRiQr1cD6J2FdyXl0jMAK250ZQ3Kxg5vmpFVhk3EYR+/5tb5S0
QeG7VqumXONTPnAzt6IHRHQyUzaLuy99zIOekKzlVGPOKbJGJQsKuRXc8nRnDN8Y
fl7C8kPHwahQqiJL3Sa6fvTVINdxkFa+VvRhX3pbAoGAVHlMTr1EkRyQfOKhB/g5
giLntGkwXG489EDPhW6MUGqLlqOIMaX4SKcg49jeARZFNe9bW9hfwzaQHVOQJTxE
CaCEaM/A0B+umbWn9s0CFMJ2D7P9s8oUNJm+srQzzIXNrHS7lzc/GDccO8ARBeBL
4+S8e3ZG3zkuKWXjlsLA/2ECgYB7XrfQRtoVtaZuOgEGJHzlqSBpFXZyV/lE+iLJ
t+b/O5LvnWVkb3VaBGHaV46iU3L6Y338NoeGco+7Gdtw3F/OtpTSR1u+hN5ftu1D
bFb8l5xET/d8kNAbsn6oWShBexW0U/l7b6NWQ5xEKUgjdMqCtP2LHNqH+WngmiUx
0MpouQKBgAkW/zgeXt5BiexQ9QLHOkpZQNdT48WTKOFDxM5b7R87E7rG6Phyx01W
tVEs5BJ9a1DizingvL2TAMglP0t5WETmkNEwIDfH8ey2KZBsfrB9Mb9BsV4xZP78
3sM6yJXbWTOMfUXByxWKdxNx9EFjes0WP0rhFgPQGCBvTZjxtAkj
-----END RSA PRIVATE KEY-----

В этом примере мы сгенерили секретный ключ для алгоритма RSA. Собственно сам ключ находится между специальными маркерами -----BEGIN RSA PRIVATE KEY----- и -----END RSA PRIVATE KEY-----. Чтобы при генерации ключа сохранить его сразу в файл, добавьте аргумент -out private.pem:

[user@shell]$ openssl genrsa -out private.pem 2048
Generating RSA private key, 2048 bit long modulus
..................................................................................................+++
......................................................+++
e is 65537 (0x10001)

С подобным форматом представления криптографических данных вам предстоит сталкиваться постоянно, называется он PEM, что расшифровывается как Privacy-enhanced Electronic Mail. Его предназначение — кодировать бинарные криптографические данные в виде ASCII-текста. И фактически между маркерами из минусов находятся закодированные в base64 бинарные данные, в данном случае — секретный ключ.

Мы можем при помощи openssl сконвертировать секретный ключ из текстового формата PEM в изначальный бинарный, сохраним его в файл private.der:

[user@shell]$ openssl rsa -inform PEM -outform DER -in private.pem -out private.der
writing RSA key

Имя формата данных DER расшифровывается как Distinguished Encoding Rules и является частью стандарта ASN.1. В стандарте ASN.1 описываются правила и структуры для кодирования, раскодирования и передачи данных по телекоммуникационным и компьютерным сетям. По сути можно считать ASN.1 форматом для сериализации структурированных бинарных данных. Существует специальный компилятор, который из формальных спецификаций ASN.1 генерирует C-код для работы с бинарными данными такой структуры.

Также существует консольная программа dumpasn1 для просмотра бинарных данных, в дебиане/убунте она ставится через sudo apt-get install dumpasn1, для макоси можно поставить через macports, через homebrew (инструкция), либо скомпилировать самостоятельно из исходников.

Вот так, например, выглядит дамп нашего созданного секретного ключа:

[user@shell]$ dumpasn1 private.der
   0 1186: SEQUENCE {
   4    1:   INTEGER 0
   7  257:   INTEGER
         :     00 CB F8 C1 9B 6E 29 63 39 3C 24 9B 2D A3 7D 00
         :     B0 B8 5C E8 D6 96 5D E4 76 67 7F 04 8F 48 D4 F4
         :     35 64 68 57 10 3D 38 CE A0 B5 DC B2 FC DD 34 FF
         :     2B AC 48 EE D1 05 37 65 4A 2F AE 8A E0 F8 1D CE
         :     63 EB 2E C6 7A 09 4B B1 85 71 1E FA FF 55 17 0C
         :     05 23 71 87 43 21 66 C4 70 6D E8 A8 B4 74 EA E4
         :     C1 75 7B AB 33 5B 8B 8D DB D4 67 BA DC B4 AD 50
         :     12 AB FB 3E 74 44 AC 48 BE 94 C2 DD 4D 16 D6 0F
         :             [ Another 129 bytes skipped ]
 268    3:   INTEGER 65537
 273  256:   INTEGER
         :     07 01 7D 4C E4 64 C1 86 B6 BD 1F 23 5B 29 30 FB
         :     E0 E9 38 0A 1E D2 0C C5 D0 5A 39 82 DE 62 8A 1C
         :     C7 5D 1A 18 71 B1 E0 CE FE 50 1D 49 B8 23 58 DC
         :     5C 27 89 24 5E C4 7F 53 23 FE 1F C1 08 64 A5 B1
         :     22 E3 D1 67 61 A8 5A E9 95 70 15 F8 ED 28 44 7E
         :     6C B0 3A 90 20 B6 91 EA B6 AB B6 17 B4 A8 58 C1
         :     18 52 EE 17 6E 7E 85 99 D6 5A D5 BD 3C EB 73 03
         :     A1 2A 99 03 8F 54 47 8F 5C 36 B1 39 33 9E 98 9D
         :             [ Another 128 bytes skipped ]
 533  129:   INTEGER
         :     00 E7 D9 F7 E8 B2 FC A4 07 93 29 B8 60 75 02 80
         :     10 EE C0 00 CF DA D8 C4 8C D0 B9 44 87 B9 ED 15
         :     3D 60 17 1E 70 1E A0 E6 31 CA 2D CB D7 2B 05 1C
         :     FF 3F 21 57 44 87 47 92 90 11 8E 7C 1D D5 57 C5
         :     FC 9B 3D 22 E2 A0 E9 5E 4E 38 B7 E8 BC B0 AC 61
         :     AB 84 C6 19 C4 2C E6 64 0A 57 54 03 D6 2A EE CD
         :     21 A1 FD BE AD B3 9B E7 2B C1 BF 37 80 84 18 A3
         :     1E D0 CF 44 12 AA B6 3A 05 3B B0 5F DF 32 B2 AE
         :     71
 665  129:   INTEGER
         :     00 E1 37 68 C5 C0 92 F7 0F 2E 99 C0 74 13 04 79
         :     DD A7 EF 56 46 A2 C9 A7 96 41 5E 4A 43 F3 57 7E
         :     3E 98 0D C6 B1 AF 85 F6 29 B9 F0 2A D2 54 8C CE
         :     C8 DB FA 67 2E 5E 56 AB CC 7B E4 F1 3B EC 55 EE
         :     10 0E B1 6F 76 A8 59 5B 2B B2 FF E2 E8 9A E5 9B
         :     F1 90 D1 65 DC 6D AD DB B3 B5 EC 39 8C 20 88 1F
         :     CA 87 E6 5A 6C 92 B1 75 F5 15 2E B1 41 0C AA 88
         :     75 88 77 E9 B2 F4 8D B6 16 E2 13 5F EF 89 20 40
         :     E5
 797  128:   INTEGER
         :     27 3A D1 60 B5 50 5C 2C CF F0 C2 3A C7 F1 A9 5B
         :     B4 1A 16 C9 14 BD 92 DC 44 C0 E4 60 96 CC 0F C8
         :     F7 C6 51 A7 24 F7 92 9B A0 1B 09 9F 99 AE DE CE
         :     2D 8F 65 A5 B9 C2 19 81 79 07 03 E7 44 5E FA A8
         :     18 58 4A DB CF E0 4C CD AD 79 28 CF 2C 91 AE 61
         :     08 31 40 D0 D9 CC 0D E7 56 09 68 30 C7 C8 EA 3A
         :     A3 9F 3C B1 45 6F BE B8 BF AA AC 28 79 B1 75 80
         :     54 52 8D B1 1E E3 80 83 BC 2A C6 BE 0C 65 01 71
 928  128:   INTEGER
         :     14 BC 57 47 2D CD DA 35 69 A2 FA 57 35 91 09 EF
         :     60 90 E6 AE A6 3A 4E D5 C4 BA FB B7 79 E6 2A 57
         :     75 04 7F B0 C8 6A 5B 19 C8 66 D6 6A 7B 22 63 BF
         :     96 91 5D 82 A5 68 F1 74 68 4B D1 F2 24 76 5C EE
         :     D9 8B 78 A9 C2 22 48 04 A3 FC 6F 55 DF 3D 18 B8
         :     8B 0E DC 84 09 0D 22 D7 4E FE AA E5 BD F1 0A 8C
         :     49 2A EA 54 68 C5 32 09 18 A4 2D E9 C1 52 CA 31
         :     98 19 02 49 59 BE DA 6F 0C ED 9F BD 9C 30 7E 09
1059  128:   INTEGER
         :     75 97 A2 6D 60 94 68 EF AB B4 3A 63 10 21 B8 AA
         :     2B 98 13 9C 0E 58 B2 FF 29 13 AB 38 18 0E FE DF
         :     C2 7D 08 46 0F D9 70 0C EA AC 86 57 C5 A3 0E EF
         :     31 C7 7A 13 8D 9B F6 5A 60 C5 1B 1C 0F C3 C3 D3
         :     C7 90 5A E2 1E A1 F0 91 CA E4 6D 6D 89 64 ED 63
         :     C8 D2 F1 8E A4 D4 56 6A 99 17 38 A6 2A 3B 35 D1
         :     92 2E 35 01 5C BF 85 31 25 F3 11 6F 73 7D F1 63
         :     99 D6 9A AC D1 B1 80 17 E7 50 2C 3A AB 88 86 58
         :   }

0 warnings, 0 errors.

В нашем файле секретного ключа «упакованы» девять целых чисел, структура ключа для алгоритма RSA весьма простая и описана, например, в RFC 3447, в секции A.1.2 RSA private key syntax.

Ну и чтобы два раза не вставать, вытащим из секретного ключа публичный ключ в файл public.der, закодированный в бинарный формат DER, openssl умеет делать и это тоже:

[user@shell]$ openssl rsa -inform DER -outform DER -in private.der -pubout -out public.der
writing RSA key

И сразу же посмотрим на его структуру через dumpasn1:

[user@shell]$ dumpasn1 public.der
  0 290: SEQUENCE {
  4  13:   SEQUENCE {
  6   9:     OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 17   0:     NULL
       :     }
 19 271:   BIT STRING, encapsulates {
 24 266:     SEQUENCE {
 28 257:       INTEGER
       :         00 CB F8 C1 9B 6E 29 63 39 3C 24 9B 2D A3 7D 00
       :         B0 B8 5C E8 D6 96 5D E4 76 67 7F 04 8F 48 D4 F4
       :         35 64 68 57 10 3D 38 CE A0 B5 DC B2 FC DD 34 FF
       :         2B AC 48 EE D1 05 37 65 4A 2F AE 8A E0 F8 1D CE
       :         63 EB 2E C6 7A 09 4B B1 85 71 1E FA FF 55 17 0C
       :         05 23 71 87 43 21 66 C4 70 6D E8 A8 B4 74 EA E4
       :         C1 75 7B AB 33 5B 8B 8D DB D4 67 BA DC B4 AD 50
       :         12 AB FB 3E 74 44 AC 48 BE 94 C2 DD 4D 16 D6 0F
       :                 [ Another 129 bytes skipped ]
289   3:       INTEGER 65537
       :       }
       :     }
       :   }

Структура публичного ключа также описана в RFC3447, в секции A.1.1, если вы туда посмотрите, то увидите, что дамп файла public.der не соответствует этому описанию. Поздравляю с первыми граблями в openssl, с ними вам тоже придётся сталкиваться постоянно. В данном случае зачем-то добавляется заголовок, указывающий, что дальше будет RSA, и на этом заголовке многим библиотекам срывает крышу.

Сертификат

Но хватит о ключах, поговорим теперь о сертификатах. Напомню, что сертификат — это обёртка над публичным ключом. Неформально файл сертификата состоит из двух частей: данных сертификата (DATA) и цифровой подписи (SIGNATURE).

CERTIFICATE
+--------------------------+
| DATA                     |
|                          |
| Version:                 |
| Subject:                 |
| Subject Public Key Info: |
| ...                      |
+--------------------------+
| SIGNATURE                |
+------------------------- +

В секции DATA содержится смысловая часть сертификата, главное поле там — Subject (он же Субъект), это владелец сертификата, именно его публичный ключ находится в поле Subject Public Key Info. Также в сертификат входят другие поля, о которых мы поговорим позже, когда будем рассматривать уточнённую схему.

В секции SIGNATURE содержится цифровая подпись всех данных из секции DATA. Напомню, что в модели PKI цифровую подпись генерирует центр авторизации, используя свой секретный ключ, удостоверяя подлинность данных сертификата.

Естественно, вся работа с сертификатами регламентируется международными стандартами, конкретно для наших сертификатов используется стандарт X.509, вот его официальный сайт: http://www.itu.int/rec/T-REC-X.509. X.509 описывает множество аспектов PKI, не только форматы данных, но ещё и алгоритмы.

Для описания структур данных используется уже знакомая нам нотация ASN.1, она достаточно простая и выразительная.

Вот так определяется сертификат (раздел 4.1. Basic Certificate Fields):

   Certificate  ::=  SEQUENCE  {
        tbsCertificate       TBSCertificate,
        signatureAlgorithm   AlgorithmIdentifier,
        signatureValue       BIT STRING  }

Это всё значит, что сертификат состоит из трёх компонентов: tbsCertificate, signatureAlgorithm и signatureValue

tbsCertificate
собственно тело сертификата, его данные, буквы tbs в названии поля расшифровываются как «to be signed», то есть это блок данных, который будет подписан, описывается ASN.1 стуктурой TBSCertificate;
signatureAlgorithm
в этом поле задаётся алгоритм подписи, определяется ASN.1 структурой AlgorithmIdentifier;
signatureValue
а в этом поле находится бинарный неструктурированный блок с собственно подписью.

Перед тем как погружаться в структуру тела сертификата, рассмотрим поле signatureAlgorithm, в его описании фигурирует очень важный для дальнейшего понимания элемент, но сначала формальное ASN.1-описание (раздел 4.1.1.2. signatureAlgorithm):

   AlgorithmIdentifier  ::=  SEQUENCE  {
        algorithm               OBJECT IDENTIFIER,
        parameters              ANY DEFINED BY algorithm OPTIONAL  }

Здесь мы видим элемент с «типом» OBJECT IDENTIFIER, это очень важная сущность, которая дальше будет встречаться везде. OBJECT IDENTIFIER часто сокращается до OID и означает буквально «идентификатор объекта». Все OID являются «словарными», то есть они определены где-то в каком-то стандарте, где также описана вся сопутствующая семантика. В нашем случае через OID определяется криптографический алгоритм, используемый для генерации цифровой подписи сертификата.

Как и другие порождения ASN.1/X.509, OID имеет чётко заданную структуру, по которой можно однозначно идентифицировать тип и свойства объекта. Существуют разные способы представления OID. Рассмотрим их на простом примере. Для генерации цифровой подписи может использоваться алгоритм, который неформально можно описать так: посчитаем SHA256 тела сертификата и дальше зашифруем это значение приватным ключом центра авторизации. Для этого алгоритма существует OID, у этого OID есть несколько равнозначных способов представления:

  1. 1.2.840.113549.1.1.11
  2. /ISO/Member-Body/840/113549/1/1/11
  3. {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) sha256WithRSAEncryption(11)}

Все эти способы отражают иерархичную сущность OID, по компонентам можно определить, например, кто внёс этот OID в реестр, какая страна, какая организация и так далее. Существует специальный сайт — http://www.oid-info.com, — на котором ведётся большой реестр всевозможных OID. В принципе, таких сайтов несколько, но этот самый популярный. Вот, например, страница OID для алгоритма sha256WithRSAEncryption: http://www.oid-info.com/get/1.2.840.113549.1.1.11.

Вернёмся теперь к сертификату, рассмотрим ASN.1-структуру TBSCertificate, вот её определение вместе с сопутствующими структурами:

   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
        }

   Version  ::=  INTEGER  {  v1(0), v2(1), v3(2)  }

   CertificateSerialNumber  ::=  INTEGER

   Validity ::= SEQUENCE {
        notBefore      Time,
        notAfter       Time }

   Time ::= CHOICE {
        utcTime        UTCTime,
        generalTime    GeneralizedTime }

   UniqueIdentifier  ::=  BIT STRING

   SubjectPublicKeyInfo  ::=  SEQUENCE  {
        algorithm            AlgorithmIdentifier,
        subjectPublicKey     BIT STRING  }

   Extensions  ::=  SEQUENCE SIZE (1..MAX) OF Extension

   Extension  ::=  SEQUENCE  {
        extnID      OBJECT IDENTIFIER,
        critical    BOOLEAN DEFAULT FALSE,
        extnValue   OCTET STRING
                    -- contains the DER encoding of an ASN.1 value
                    -- corresponding to the extension type identified
                    -- by extnID
        }

Я не буду полностью описывать семантику этих полей, это всё приводится с подробностями в разделе 4.1. Basic Certificate Fields RFC 5280. Так, коротко пробежимся по основным полям.

В поле signature должно быть то же самое значение, что и в поле signatureAlgorithm, то есть алгоритм, которым CA подписывает сертификат.

В поле issuer находится имя центра авторизации, в поле subject — имя владельца сертификата. Оба этих имени также являются структурированными (ASN.1-структура Name) и должны быть непустыми, их тип определяется стандартом X.501 и носит название distinguished name, сокращённо DN. Это тот же самый DN, что и в LDAP.

Поля в структуре Name также задаются через OID. Вот небольшой пример, как поле issue «разворачивается» из текстового представления в строго формализованное. Возьмём DN-фрагмент реального сертификата (https://google.ru):

C=US, O=Google Inc, CN=Google Internet Authority G2

Небольшой хинт, как скачать сертификат с сайта и сразу конвертнуть его в DER в файл certificate.der:

openssl s_client -connect google.ru:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | openssl x509 -out certificate.der -outform DER

А вот, в какую ASN.1-структуру «разворачивается» этот текст:

SEQUENCE {
  SET {
    SEQUENCE {
      OBJECT IDENTIFIER countryName (2 5 4 6)
      PrintableString 'US'
      }
    }
  SET {
    SEQUENCE {
      OBJECT IDENTIFIER organizationName (2 5 4 10)
      PrintableString 'Google Inc'
      }
    }
  SET {
    SEQUENCE {
      OBJECT IDENTIFIER commonName (2 5 4 3)
      PrintableString 'Google Internet Authority G2'
      }
    }
  }

По факту это последовательность пар ключ-значение, где в роли ключа выступает OID. То есть кажущаяся обычным текстом строка оказывается одной из форм представления строго структурированного объекта. Каждая такая пара ключ-значение называется RelativeDistinguishedName, в роли ключа выступает OID.

Для дальнейшего изучения темы нам понадобится настоящий живой сертификат. Я взял сертификат c twitter.com, вы его можете скачать сами (если знаете, как это сделать вашим браузером) или взять сохранённую мной версию в виде DER-файла (скачать сохранённую версию).

Итак, сначала посмотрим, как сертификат выглядит в «человечном» формате, вот его полная распечатка (немного отформатированная, чтобы длинная строка не распирала экран):

[user@shell]$ openssl x509 -inform DER -in certificate-twitter.com.der -noout -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            1a:c8:5e:b7:ae:c3:51:3c:d8:0d:85:38:5e:cf:d2:08
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=US, O=Symantec Corporation, OU=Symantec Trust Network, CN=Symantec Class 3 EV SSL CA - G3
        Validity
            Not Before: Sep 10 00:00:00 2014 GMT
            Not After : May  9 23:59:59 2016 GMT
        Subject: 1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/
businessCategory=Private Organization/serialNumber=4337446, C=US/postalCode=94103-1307, 
ST=California, L=San Francisco/street=1355 Market St, O=Twitter, Inc., 
OU=Twitter Security, CN=twitter.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public Key: (2048 bit)
                Modulus (2048 bit):
                    00:e3:ac:59:34:07:dc:11:f8:1c:ca:b3:0f:93:44:
                    8a:54:34:76:90:6a:c0:22:00:be:95:9a:da:58:3c:
                    6c:38:31:a2:a2:1f:3b:64:e2:9d:e0:f5:c2:ab:07:
                    90:5b:7c:fe:f9:88:8c:6a:9d:69:3b:e0:23:65:b7:
                    11:d6:e8:88:d6:3e:6d:8b:ed:ca:ea:58:0b:fe:4d:
                    bf:2a:95:ca:bb:21:bb:ce:d6:e2:10:02:11:21:68:
                    26:f7:92:7e:9c:a3:80:b1:82:d7:e5:a6:a0:86:47:
                    42:1a:c6:5b:04:d9:c3:b5:b2:9b:38:d4:a1:6d:3b:
                    bd:d8:05:f0:51:9b:bd:95:77:7f:e9:02:8e:60:a3:
                    7a:65:20:52:23:db:8d:01:27:24:c2:00:66:0d:14:
                    66:b3:52:2b:cc:6b:5b:a5:44:2f:e2:40:6d:da:21:
                    a1:92:5a:57:12:d3:47:01:ef:e9:df:af:c6:91:8c:
                    21:af:77:65:13:36:1c:63:7a:2d:05:e6:63:c5:0b:
                    d8:39:e9:ac:f2:3b:ff:9d:c5:a7:46:0a:6e:1a:66:
                    10:1e:4a:e7:ba:c7:89:79:1f:ae:f1:f3:84:03:ca:
                    e7:50:8a:19:63:bf:3c:20:10:78:c5:f4:53:3c:7d:
                    5e:0d:af:96:70:89:92:b9:7f:9a:19:0c:f6:78:6a:
                    8f:73
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Alternative Name:
                DNS:twitter.com, DNS:www.twitter.com
            X509v3 Basic Constraints:
                CA:FALSE
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Certificate Policies:
                Policy: 2.16.840.1.113733.1.7.23.6
                  CPS: https://d.symcb.com/cps
                  User Notice:
                    Explicit Text: https://d.symcb.com/rpa

            X509v3 Authority Key Identifier:
                keyid:01:59:AB:E7:DD:3A:0B:59:A6:64:63:D6:CF:20:07:57:D5:91:E7:6A

            X509v3 CRL Distribution Points:
                URI:http://sr.symcb.com/sr.crl

            Authority Information Access:
                OCSP - URI:http://sr.symcd.com
                CA Issuers - URI:http://sr.symcb.com/sr.crt

    Signature Algorithm: sha256WithRSAEncryption
        d1:53:68:e9:d6:20:d0:56:7a:10:80:b8:e9:7e:00:c9:9e:d5:
        35:4a:a2:d2:a0:16:8a:e2:fb:eb:96:88:77:c2:6e:35:f4:a7:
        a9:aa:dc:35:7b:c6:7d:5e:3c:f6:c9:5b:a0:d1:58:ae:7d:96:
        e7:54:02:5c:69:1b:56:92:26:ad:06:2c:c1:5a:ff:59:f3:8a:
        8c:94:32:0d:1a:42:d1:6e:bc:1c:bd:a8:c6:08:01:1b:73:17:
        93:28:30:ae:ce:4d:4e:2d:4b:bf:22:af:9a:61:32:7a:a8:68:
        25:19:3c:6d:fb:67:cc:29:3f:5b:f5:d1:af:4c:bf:67:a3:60:
        c4:dd:b0:fb:83:55:6d:b5:2c:a9:7d:34:ad:b0:08:c7:2c:f0:
        cb:4c:d8:2b:79:f4:e9:da:7f:6e:c0:de:55:7c:d6:d6:47:cf:
        c4:90:ef:4f:be:eb:c9:3d:05:71:6b:5e:c7:36:8d:4f:0c:3c:
        47:83:a5:11:88:22:f8:46:e0:f8:9b:1a:fe:e9:a2:df:90:81:
        10:71:f3:97:9c:b7:69:60:77:20:d6:87:85:ee:5a:77:d2:92:
        ec:d9:5d:1f:31:3b:3a:e2:5b:35:d1:92:36:db:44:d4:79:d9:
        6c:03:24:87:5d:c3:86:c6:10:e2:ea:65:7c:cf:b8:ef:c2:31:
        02:55:72:12

Разберём последовательно поля сертификата.

Version: 3 (0x2)
Это версия не конкретно этого сертификата, а используемого «профайла», на данный момент таких версий всего три: 1, 2, 3; чаще всего используется 3. Фактическое значение поля — 2, так как нумерация версий идёт с нуля.
Serial Number: 1a:c8:5e:b7:ae:c3:51:3c:d8:0d:85:38:5e:cf:d2:08
Это «серийный номер» сертификата, по сути он является уникальным идентификатором в базе CA, для каждого подписанного сертификата этот номер должен быть свой.
Signature Algorithm: sha256WithRSAEncryption
Это мы уже знаем: для подписывания сертификата используется алгоритм sha256WithRSAEncryption
Issuer: C=US, O=Symantec Corporation, OU=Symantec Trust Network, CN=Symantec Class 3 EV SSL CA - G3
Это имя (distinguished name, DN) центра авторизации, является по сути идентификатором в браузерной базе CA-сертификатов, и чтобы корректно проверифицировать серверный сертификат, браузер должен у себя иметь CA-сертификат, в поле subject которого стоит точно такое же значение, как здесь.
Validity
В этом поле две даты, с какой и по какую сертификат считается валидным. Это поле является главной кормушкой центров авторизации: каждые год или два нужно выписывать (=подписывать у центра авторизации) новый сертификат, не бесплатно, конечно.
Subject: 1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware/businessCategory=Private Organization/serialNumber=4337446, C=US/postalCode=94103-1307, ST=California, L=San Francisco/street=1355 Market St, O=Twitter, Inc., OU=Twitter Security, CN=twitter.com
Субъект. Тот, кому выписан сертификат. Это DN, то есть структурированное поле. Обратите внимание на самые первые компоненты — 1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=Delaware, в них мы видим, что openssl не смог распознать эти OID и вывели их в «сыром» виде. Вот они в репозитории OID: 1.3.6.1.4.1.311.60.2.1.3 jurisdictionOfIncorporationCountryName, 1.3.6.1.4.1.311.60.2.1.2 jurisdictionOfIncorporationStateOrProvinceName. Дальше идут уже распознанные компоненты, в качестве разделителя компонентов используется запятая или слеш, такие особенности вывода openssl.
Обратите внимание на компонент CN, это Common name, именно здесь находится имя домена, для которого выписывается сертификат.
Subject Public Key Info
Этот блок содержит публичный ключ субъекта, синтаксически там два поля: OID алгоритма шифрования и собственно тело публичного ключа. Openssl при показе сертификата пытается показать детали публичного ключа, в данном случае он показывает модуль и экспоненту RSA-ключа. Ниже я покажу, как openssl справляется с сертификатми, с алгоритмами которых он не знаком.
X509v3 extensions
В этом блоке содержатся так называемые расширения сертификата. Хотя структура сертификата очень жёсткая, в ней были оставлены точки расширения, чтобы можно было в сертификат добавлять дополнительные данные, которые изначально не предусмотрели. Фактически, блок с расширениями — это набор пар OID-значение, где тип значения зависит от конкретного OID. Расширения допускаются только для сертификатов версии 3.
Обычно в расширениях указываются дополнительные параметры сертификата. Например, в поле X509v3 Subject Alternative Name указываются дополнительные доменные адреса сайта, для которых сертификат применим. Или, например, в поле X509v3 Basic Constraints, указываются ограничения в применимости сертификата, в данном случае указано, что сертификат не может использоваться в качестве сертификата удостоверяющего центра.
Signature Algorithm
Этим блоком сертификат всегда завершается, в нём содержится идентификатор алгоритма подписи и собственно подпись вышестоящего блока.

Дальше я рекомендую выполнить команду dumpasn1 certificate-twitter.com.der и сравнить только что прочитанное с показанным в дампе, параллельно полезно держать открытыми RFC 5280 и RF 3280.

Цепочки удостоверяющих сертификатов

Удостоверяющий центр может выдать сертификат, в котором в поле X509v3 Basic Constraints разрешается его использование как удостоверяющего. Этим сертификатом тоже можно подписывать «доменные» сертификаты. Таким образом, все CA-сертификаты образуют цепочку, а в целом — дерево. В вершине этого дерева находится Самый Главный Сертификат, он называется корневым сертификатом (по-английски root certificate).

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

Естественно, центры авторизации не выписывают CA-сертификаты кому попало, но крупная организация может этого добиться, например, у Google есть свой CA-сертификат.

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

Корневой сертификат является самоподписанным, то есть он подписан тем же самым ключом, публичный ключ для которого находится в теле сертификата. У корневого сертификата совпадают поля issuer и subject.

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

Как вручную проверить цепочку сертификатов

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

Возьмём три сертификата из Go Daddy, вот они: GD_root.pem (это корневой сертификат Go Daddy), GD_good.pem, GD_bad.pem. Нам нужно проверить, подписаны ли сертификаты GD_good.pem и GD_bad.pem корневым сертификатом Go Daddy.

Для верификации используем команду openssl verify, в ней мы должны указать файл с «доверенными» сертификатами, в нашем случае это один сертификат GD_root.pem, указывается через аргумент -CAfile GD_root.pem. Также мы указываем заведомо несуществующий путь к главному хранилищу сертификатов удостоверяющих центров, чтобы в процессе верификации участвовал только один сертификат, это делается через аргумент -CApath /nope.

[user@shell]$ % openssl verify -verbose -x509_strict -CApath /nope -CAfile GD_root.pem GD_bad.pem
GD_bad.pem: C = US, ST = Arizona, L = Scottsdale, O = "GoDaddy.com, Inc.", OU = https://certs.godaddy.com/repository/, 
CN = Go Daddy Root Certificate Authority - G2
error 20 at 0 depth lookup:unable to get local issuer certificate
[user@shell]$ % openssl verify -verbose -x509_strict -CApath /nope -CAfile GD_root.pem GD_good.pem
GD_good.pem: OK

И ещё о сертификатах

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

Вот как выглядит попытка распечатать его как текст:

[user@shell]$ % openssl x509 -inform DER -in KISC_ROOT_CA_GOST.der -noout -text
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
90096:error:0D09B0A3:asn1 encoding routines:d2i_PublicKey:unknown public key type:/SourceCache/OpenSSL098/OpenSSL098-52.40.1/src/crypto/asn1/d2i_pu.c:125:
90096:error:0B077066:x509 certificate routines:X509_PUBKEY_get:err asn1 lib:/SourceCache/OpenSSL098/OpenSSL098-52.40.1/src/crypto/asn1/x_pubkey.c:366:
        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

Обратите внимание на поле Signature Algorithm: 1.3.6.1.4.1.6801.1.2.2, openssl не смог распознать этот алгоритм, так как в его коде нет такого OID. Если мы попытаемся найти этот OID в репозитории http://www.oid-info.com/get/1.3.6.1.4.1.6801.1.2.2, то обнаружим, что им он неизвестен. Но зато им известен «родительский» OID (помним, что схема OID иерархичная), по адресу http://www.oid-info.com/get/1.3.6.1.4.1.6801 находим координаты владельца этой схемы — казахстанская фирма Gamma Technologies, OID предыдущего уровня (1.3.6.1.4.1.6801.1.2) выдаёт нам GOST28147, https://ru.wikipedia.org/wiki/ГОСТ_28147-89.

Как мы видим, фирма использует X.509 для хранения своих собственных сертификатов. Браузер его не сможет переварить, а вот специализированный софт — вполне. Например, dumpasn1 отлично показывает структуру ASN.1-объектов.

Следующий файл для препарирования — сертификат удостоверяющего центра Бурятии, скачайте файл ca-bur.der. Предлагаю вам самим его скачать и запустить команду:

[user@shell]$ % openssl x509 -inform DER -in ca-bur.der -noout -text

А затем такую, для контраста:

[user@shell]$ % openssl x509 -inform DER -in ca-bur.der -noout -text -nameopt multiline,-esc_msb,utf8

Комментарии

Текст комментария (разметка: *курсив*, **полужирная**, [ссылка](http://example.com) или <http://example.com> ещё)
Имя (обязательно, 50 символов или меньше)
Email, на который получать ответы (не будет опубликован)
Веб-сайт
© 2006—2016 Sergey Stolyarov | Работает на Pyrone