Разбираем x.509 сертификат


    Привет, %username%!

    Так уж вышло, что несмотря на относительно неплохое понимание инфраструктуры открытых ключей, содержимое *.crt файлов всегда оставалось для меня полнейшей загадкой.
    Нет, не поймите неправильно. Я знаю, что x.509 сертификат содержит информацию о владельце, открытый ключ, сведения об удостоверяющем центре и электронную цифровую подпись. Но при установке очередного сертификата меня всегда мучило любопытство.
    Чем отличается идентификатор ключа от отпечатка? Какие данные сертификата подписываются, а какие нет? И что за структура данных позволяет хранить всю эту информацию, сводя избыточность к минимуму.
    Но вот наконец-то любопытство перебороло лень и в данном посте я постараюсь описать структуру x.509 сертификатов и ответить на эти и другие вопросы.

    Часть 1. Самоподписанный сертификат


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

    Сделать это можно с помощью библиотеки Bouncy Castle, следующим образом:
    private void button1_Click(object sender, EventArgs e)
            {            
    
                var KeyGenerate = new RsaKeyPairGenerator();
    
                KeyGenerate.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
    
                AsymmetricCipherKeyPair kp = KeyGenerate.GenerateKeyPair();
    
                var gen = new X509V3CertificateGenerator();
    
                var certName = new X509Name("CN=CA");
                var serialNo = new BigInteger("1",10);            
    
                gen.SetSerialNumber(serialNo);
                gen.SetSubjectDN(certName);            
                gen.SetIssuerDN(certName);
                gen.SetNotAfter(DateTime.Now.AddYears(100));
                gen.SetNotBefore(DateTime.Now);
                gen.SetSignatureAlgorithm("SHA1WITHRSA");            
                gen.SetPublicKey(kp.Public);     
                var myCert = gen.Generate(kp.Private);
                byte[] result = DotNetUtilities.ToX509Certificate(myCert).Export(X509ContentType.Cert);
    
                FileStream fs = new FileStream("D:\\test1.crt", FileMode.CreateNew);
                fs.Write(result, 0, result.Length);
                fs.Flush();
                fs.Close();
            }
    


    В результате выполнения данной процедуры будет создан стандартный x.509 сертификат, который, будучи открытым с помощью hex-редактора, выглядит вот таким чудесным образом:
    30 82 01 8F 30 81 F9 A0  03 02 01 02 02 01 01 30
    0D 06 09 2A 86 48 86 F7  0D 01 01 05 05 00 30 0D
    31 0B 30 09 06 03 55 04  03 0C 02 43 41 30 20 17
    0D 31 33 30 39 31 35 31  35 33 35 30 32 5A 18 0F
    32 31 31 33 30 39 32 32  31 35 33 35 30 32 5A 30
    0D 31 0B 30 09 06 03 55  04 03 0C 02 43 41 30 81
    9F 30 0D 06 09 2A 86 48  86 F7 0D 01 01 01 05 00
    03 81 8D 00 30 81 89 02  81 81 00 8D 80 B5 8E 80
    8E 94 D1 04 03 6A 45 1A  54 5E 7E EE 6D 0C CB 0B
    82 03 F1 7D C9 6F ED 52  02 B2 08 C3 48 D1 24 70
    C3 50 C2 1C 40 BC B5 9D  F8 E8 A8 41 16 7B 0B 34
    1F 27 8D 32 2D 38 BA 18  A5 31 A9 E3 15 20 3D E4
    0A DC D8 CD 42 B0 E3 66  53 85 21 7C 90 13 E9 F9
    C9 26 5A F3 FF 8C A8 92  25 CD 23 08 69 F4 A2 F8
    7B BF CD 45 E8 19 33 F1  AA E0 2B 92 31 22 34 60
    27 2E D7 56 04 8B 1B 59  64 77 5F 02 03 01 00 01
    30 0D 06 09 2A 86 48 86  F7 0D 01 01 05 05 00 03
    81 81 00 0A 1C ED 77 F4  79 D5 EC 73 51 32 25 09
    61 F7 00 C4 64 74 29 86  5B 67 F2 3D A9 39 34 6B
    3C A9 92 B8 BF 07 13 0B  A0 9B DF 41 E2 8A F6 D3
    17 53 E1 BA 7F C0 D0 BC  10 B7 9B 63 4F 06 D0 7B
    AC C6 FB CE 95 F7 8A 72  AA 10 EA B0 D1 6D 74 69
    5E 20 68 5D 1A 66 28 C5  59 33 43 DB EE DA 00 80
    99 5E DD 17 AC 43 36 1E  D0 5B 06 0F 8C 6C 82 D3
    BB 3E 2B A5 F1 94 FB 53  7B B0 54 22 6F F6 4C 18
    1B 72 1C
    

    Тот же самый сертификат, но уже открытый с помощью стандартных средств windows:
    Имя сертификата	CA
    Издатель	CA
    Версия сертификата	3
    Серийный номер	0x1
    Недействителен до...	15.09.2013 15:35:00 GMT
    Недействителен после...	22.09.2113 15:35:00 GMT
    Цифровая подпись (SHA-1)	F9 AD 58 B5 50 3D F6 36 5E B8 89 D4 DC C8 5F CC 25 4B 93 A2
    Цифровая подпись (SHA-256)	42 02 24 20 4E 8F 3A 3E 31 38 88 E5 C5 E7 C3 03 14 3A A6 52 EA 78 B9 77 42 5B 99 EB 4B BA 23 82
    Открытый ключ(1024 битный)		Алгоритм открытого ключа	rsaEncryption
    Модуль	
    00: 8D 80 B5 8E 80 8E 94 D1 04 03 6A 45 1A 54 5E 7E
    10: EE 6D 0C CB 0B 82 03 F1 7D C9 6F ED 52 02 B2 08
    20: C3 48 D1 24 70 C3 50 C2 1C 40 BC B5 9D F8 E8 A8
    30: 41 16 7B 0B 34 1F 27 8D 32 2D 38 BA 18 A5 31 A9
    40: E3 15 20 3D E4 0A DC D8 CD 42 B0 E3 66 53 85 21
    50: 7C 90 13 E9 F9 C9 26 5A F3 FF 8C A8 92 25 CD 23
    60: 08 69 F4 A2 F8 7B BF CD 45 E8 19 33 F1 AA E0 2B
    70: 92 31 22 34 60 27 2E D7 56 04 8B 1B 59 64 77 5F
    Экспонента	01 00 01                                       
    
    Подпись		Алгоритм подписи	sha1WithRSAEncryption
    Подпись	
    00: 0A 1C ED 77 F4 79 D5 EC 73 51 32 25 09 61 F7 00
    10: C4 64 74 29 86 5B 67 F2 3D A9 39 34 6B 3C A9 92
    20: B8 BF 07 13 0B A0 9B DF 41 E2 8A F6 D3 17 53 E1
    30: BA 7F C0 D0 BC 10 B7 9B 63 4F 06 D0 7B AC C6 FB
    40: CE 95 F7 8A 72 AA 10 EA B0 D1 6D 74 69 5E 20 68
    50: 5D 1A 66 28 C5 59 33 43 DB EE DA 00 80 99 5E DD
    60: 17 AC 43 36 1E D0 5B 06 0F 8C 6C 82 D3 BB 3E 2B
    70: A5 F1 94 FB 53 7B B0 54 22 6F F6 4C 18 1B 72 1C
    

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

    Прежде всего, нужно отметить, что файл *.crt хранит информацию о сертификате в закодированном виде. Для кодирования применяется особый язык, называемый ASN.1.
    ASN.1 — стандарт записи, описывающий структуры данных для представления, кодирования, передачи и декодирования данных. Wikipedia

    С помощью языка ASN.1 можно описывать сложные структуры, состоящие из данных различных типов. Типичный пример ASN.1-файла выглядит как-то так:
    ASN.1-файл
    SEQUENCE(3 elem)
    	SEQUENCE(7 elem)
    		[0](1 elem)
    			INTEGER 2
    		INTEGER 1
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 1.2.840.113549.1.1.5
    			NULL
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			UTCTime 13-09-15 15:35:02 UTC
    			GeneralizedTime 2113-09-22 15:35:02 UTC
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			SEQUENCE(2 elem)
    				OBJECT IDENTIFIER 1.2.840.113549.1.1.1
    				NULL
    			BIT STRING(1 elem)
    				SEQUENCE(2 elem)
    					INTEGER(1024 bit)
    					INTEGER 65537
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 1.2.840.113549.1.1.5
    			NULL
    	BIT STRING(1024 bit)
    


    Однако ASN.1 разрабатывался в те светлые времена, когда «640 КБ должно было хватать каждому» и тратить место на такую громоздкую запись не было никакой возможности. Поэтому, в целях экономии места, а также более удобной обработки хранимой в ASN.1-форме информации, был разработан специальный метод кодирования — DER.

    DER-кодировка описывается следующим правилом. Первым записывается байт, характеризующий тип данных, затем последовательность байтов хранящих сведения о длине данных и затем уже записываются сами данные.

    К примеру, для кодировки целого числа INTEGER 65537 используется следующая форма: 02 03 01 00 01.
    Здесь первый байт 02, определяет тип INTEGER (полную таблицу типов вы можете найти например тут), второй байт 03 показывает длину блока. А следующие за этим байты 01 00 01, являются шестнадцатеричной записью нашего числа 65537.

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

    Наименование типа Краткое описание Представление типа в DER-кодировке
    SEQUENCE Используется для описания структуры данных, состоящей из различных типов. 30
    INTEGER Целое число. 02
    OBJECT IDENTIFIER Последовательность целых чисел. 06
    UTCTime Временной тип, содержит 2 цифры для определения года 17
    GeneralizedTime Расширенный временной тип, содержит 4 цифры для обозначения года. 18
    SET Описывает структуру данных разных типов. 31
    UTF8String Описывает строковые данные. 0C
    NULL Собственно NULL 05
    BIT STRING Тип для хранения последовательности бит. 03


    Зная как кодируется каждый из этих типов, мы можем попытаться распарсить наш *.crt файл.
    30 82 01 8F 30 81 F9 A0 03 02 01 02 02 01 01 30
    0D 06 09 2A 86 48 86 F7 0D 01 01 05 05 00 30 0D
    31 0B 30 09 06 03 55 04 03 0C 02 43 41 30 20 17
    0D 31 33 30 39 31 35 31 35 33 35 30 32 5A 18 0F
    32 31 31 33 30 39 32 32 31 35 33 35 30 32 5A 30
    0D 31 0B 30 09 06 03 55 04 03 0C 02 43 41 30 81
    9F 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00
    03 81 8D 00 30 81 89 02 81 81 00 8D 80 B5 8E 80
    8E 94 D1 04 03 6A 45 1A 54 5E 7E EE 6D 0C CB 0B
    82 03 F1 7D C9 6F ED 52 02 B2 08 C3 48 D1 24 70
    C3 50 C2 1C 40 BC B5 9D F8 E8 A8 41 16 7B 0B 34
    1F 27 8D 32 2D 38 BA 18 A5 31 A9 E3 15 20 3D E4
    0A DC D8 CD 42 B0 E3 66 53 85 21 7C 90 13 E9 F9
    C9 26 5A F3 FF 8C A8 92 25 CD 23 08 69 F4 A2 F8
    7B BF CD 45 E8 19 33 F1 AA E0 2B 92 31 22 34 60
    27 2E D7 56 04 8B 1B 59 64 77 5F 02 03 01 00 01
    30 0D 06 09 2A 86 48 86 F7 0D 01 01 05 05 00 03
    81 81 00 0A 1C ED 77 F4 79 D5 EC 73 51 32 25 09
    61 F7 00 C4 64 74 29 86 5B 67 F2 3D A9 39 34 6B
    3C A9 92 B8 BF 07 13 0B A0 9B DF 41 E2 8A F6 D3
    17 53 E1 BA 7F C0 D0 BC 10 B7 9B 63 4F 06 D0 7B
    AC C6 FB CE 95 F7 8A 72 AA 10 EA B0 D1 6D 74 69
    5E 20 68 5D 1A 66 28 C5 59 33 43 DB EE DA 00 80
    99 5E DD 17 AC 43 36 1E D0 5B 06 0F 8C 6C 82 D3
    BB 3E 2B A5 F1 94 FB 53 7B B0 54 22 6F F6 4C 18
    1B 72 1C

    Преобразуя байты-идентификаторы типов и убирая байты описывающие длину блоков получим следующую структуру:
    SEQUENCE(3 elem)
    	SEQUENCE(7 elem)
    		[0](1 elem)
    			INTEGER 2
    		INTEGER 1
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 1.2.840.113549.1.1.5
    			NULL
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			UTCTime 13-09-15 15:35:02 UTC
    			GeneralizedTime 2113-09-22 15:35:02 UTC
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			SEQUENCE(2 elem)
    				OBJECT IDENTIFIER 1.2.840.113549.1.1.1
    				NULL
    			BIT STRING(1 elem)
    				SEQUENCE(2 elem)
    					INTEGER 00: 8D 80 B5 8E 80 8E 94 D1 04 03 6A 45 1A 54 5E 7E
    						        EE 6D 0C CB 0B 82 03 F1 7D C9 6F ED 52 02 B2 08
    						        C3 48 D1 24 70 C3 50 C2 1C 40 BC B5 9D F8 E8 A8
    						        41 16 7B 0B 34 1F 27 8D 32 2D 38 BA 18 A5 31 A9
    						        E3 15 20 3D E4 0A DC D8 CD 42 B0 E3 66 53 85 21
    						        7C 90 13 E9 F9 C9 26 5A F3 FF 8C A8 92 25 CD 23
    						        08 69 F4 A2 F8 7B BF CD 45 E8 19 33 F1 AA E0 2B
    						        92 31 22 34 60 27 2E D7 56 04 8B 1B 59 64 77 5F
    					INTEGER 65537
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 1.2.840.113549.1.1.5
    			NULL
    	BIT STRING 00: 0A 1C ED 77 F4 79 D5 EC 73 51 32 25 09 61 F7 00
    		           C4 64 74 29 86 5B 67 F2 3D A9 39 34 6B 3C A9 92
    		           B8 BF 07 13 0B A0 9B DF 41 E2 8A F6 D3 17 53 E1
    		           BA 7F C0 D0 BC 10 B7 9B 63 4F 06 D0 7B AC C6 FB
    		           CE 95 F7 8A 72 AA 10 EA B0 D1 6D 74 69 5E 20 68
    		           5D 1A 66 28 C5 59 33 43 DB EE DA 00 80 99 5E DD
    		           17 AC 43 36 1E D0 5B 06 0F 8C 6C 82 D3 BB 3E 2B
    		           A5 F1 94 FB 53 7B B0 54 22 6F F6 4C 18 1B 72 1C
    

    Это уже более похоже на то, что мы видим при открытии сертификатов в браузере или Windows. Пробежимся по каждому элементу:
    • INTEGER 2 — целое число, описывающее версию сертификата. Для сертификатов версии 1 равно 0.
    • INTEGER 1 — серийный номер нашего сертификата.
    • OBJECT IDENTIFIER 1.2.840.113549.1.1.5 — последовательность, описывающая алгоритм цифровой подписи. Данная последовательность описывает sha1WithRSAEncryption.
    • OBJECT IDENTIFIER 2.5.4.3 — служит индикатором того, что следующее поле описывает какое либо сведение об издателе. Последовательность 2.5.4.3, описывается свойство CN(common name) — общепринятое имя.
    • UTF8String CA — имя издателя.
    • UTCTime 13-09-15 15:35:02 UTC — дата начала действия сертификата.
    • GeneralizedTime 2113-09-22 15:35:02 UTC — дата окончания действия сертификата.
    • OBJECT IDENTIFIER 2.5.4.3 — описывает тип информации о владельце.
    • UTF8String CA — имя владельца.
    • OBJECT IDENTIFIER 1.2.840.113549.1.1.1 — характеризует алгоритм ключа, в данном случае rsaEncryption.
    • INTEGER 00: — открытый ключ сертификата.
    • BIT STRING 00: — подпись сертификата.


    Важным моментом, о котором стоит особенно упомянуть являются данные, для которых вычисляется подпись. Интуитивно может показаться, что подписываются все данные идущие до последнего поля BIT STRING, содержащего подпись. Но на самом деле это не так. В стандарте x.509 подписывается определенная часть сертификата, называемая TBS-сертификат (to be signed). В TSB-сертификат входит последовательность SEQUENCE второго уровня со всеми вложенными данными.
    	SEQUENCE(7 elem)
    		[0](1 elem)
    			INTEGER 2
    		INTEGER 1
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 1.2.840.113549.1.1.5
    			NULL
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			UTCTime 13-09-15 15:35:02 UTC
    			GeneralizedTime 2113-09-22 15:35:02 UTC
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER 2.5.4.3
    					UTF8String CA
    		SEQUENCE(2 elem)
    			SEQUENCE(2 elem)
    				OBJECT IDENTIFIER 1.2.840.113549.1.1.1
    				NULL
    			BIT STRING(1 elem)
    				SEQUENCE(2 elem)
    					INTEGER 00: 8D 80 B5 8E 80 8E 94 D1 04 03 6A 45 1A 54 5E 7E
    						        EE 6D 0C CB 0B 82 03 F1 7D C9 6F ED 52 02 B2 08
    						        C3 48 D1 24 70 C3 50 C2 1C 40 BC B5 9D F8 E8 A8
    						        41 16 7B 0B 34 1F 27 8D 32 2D 38 BA 18 A5 31 A9
    						        E3 15 20 3D E4 0A DC D8 CD 42 B0 E3 66 53 85 21
    						        7C 90 13 E9 F9 C9 26 5A F3 FF 8C A8 92 25 CD 23
    						        08 69 F4 A2 F8 7B BF CD 45 E8 19 33 F1 AA E0 2B
    						        92 31 22 34 60 27 2E D7 56 04 8B 1B 59 64 77 5F
    					INTEGER 65537
    

    Т.о. если перед вами будет стоять задача проверить ЭЦП x.509 сертификата, то для этого сперва необходимо извлечь TBS-сертификат.

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

    Часть 2. Сертификат 2-го уровня


    Мы с вами рассмотрели внутренности самоподписанного сертификата, и нам осталось понять чем отличается структура сертификатов более низкого уровня, от сертификата корневого центра.
    Для этого, с помощью имеющегося у нас секретного ключа сертификата CA, создадим подчиненный ему сертификат user. И в этом нам снова поможет Bouncy Castle.
    private void button2_Click(object sender, EventArgs e)
            {            
    
                var KeyGenerate = new RsaKeyPairGenerator();
    
                KeyGenerate.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
    
                AsymmetricCipherKeyPair kp2 = kpgen.GenerateKeyPair();
    
                var gen2 = new X509V3CertificateGenerator();
    
                var certName = new X509Name("CN=CA");
                var serialNo = new BigInteger("1",10);   
    
                var certName2 = new X509Name("CN=User");
                var certNameOwner2 = new X509Name("CN=User");
                var serialNo2 = new BigInteger("2", 10);            
    
                gen2.SetSerialNumber(serialNo2);
                gen2.SetSubjectDN(certName2);
                gen2.SetIssuerDN(certName);
                gen2.SetNotAfter(DateTime.Now.AddYears(100));
                gen2.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
                gen2.SetSignatureAlgorithm("SHA1WITHRSA");
                gen2.SetPublicKey(kp2.Public);
                gen2.AddExtension(
                    X509Extensions.AuthorityKeyIdentifier.Id,
                    false,
                    new AuthorityKeyIdentifier(
                        SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(kp.Public),
                        new GeneralNames(new GeneralName(certName)),
                        serialNo));
    
                var newCert2 = gen2.Generate(kp.Private);
                byte[] result = DotNetUtilities.ToX509Certificate(newCert2).Export(X509ContentType.Cert);
                FileStream fs = new FileStream("D:\\FullTest.crt", FileMode.CreateNew);
                fs.Write(result, 0, result.Length);
                fs.Flush();
                fs.Close();
            }
    


    Распарсив наш сертификат и преобразовав его к читаемому виду, получим следующую красоту:
    SEQUENCE(3 elem)
    	SEQUENCE(8 elem)
    		[0](1 elem)
    			INTEGER2
    		INTEGER2
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER1.2.840.113549.1.1.5
    			NULL
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER2.5.4.3
    					UTF8StringCA
    		SEQUENCE(2 elem)
    			UTCTime13-09-15 15:35:02 UTC
    			GeneralizedTime2113-09-22 15:35:02 UTC
    		SEQUENCE(1 elem)
    			SET(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER2.5.4.3
    					UTF8StringUser
    		SEQUENCE(2 elem)
    			SEQUENCE(2 elem)
    				OBJECT IDENTIFIER1.2.840.113549.1.1.1
    				NULL
    			BIT STRING(1 elem)
    				SEQUENCE(2 elem)
    					INTEGER(1024 bit)
    					INTEGER65537
    		[3](1 elem)
    			SEQUENCE(1 elem)
    				SEQUENCE(2 elem)
    					OBJECT IDENTIFIER2.5.29.35
    					OCTET STRING(1 elem)
    						SEQUENCE(3 elem)
    							[0](20 byte) 6FBC9476035CB50061524C4ABE9064C9C4C32E6B
    							[1](1 elem)
    								[4](1 elem)
    									SEQUENCE(1 elem)
    										SET(1 elem)
    											SEQUENCE(2 elem)
    												OBJECT IDENTIFIER2.5.4.3
    												UTF8StringCA
    							[2](1 byte) 01
    	SEQUENCE(2 elem)
    		OBJECT IDENTIFIER1.2.840.113549.1.1.5
    		NULL
    	BIT STRING(1024 bit)
    

    Как видите, единственное отличие от самоподписанного сертификата заключается в наличие дополнительного блока:
    [3](1 elem)
    	SEQUENCE(1 elem)
    		SEQUENCE(2 elem)
    			OBJECT IDENTIFIER 2.5.29.35
    			OCTET STRING(1 elem)
    				SEQUENCE(3 elem)
    					[0](20 byte) 6FBC9476035CB50061524C4ABE9064C9C4C32E6B
    					[1](1 elem)
    						[4](1 elem)
    							SEQUENCE(1 elem)
    								SET(1 elem)
    									SEQUENCE(2 elem)
    										OBJECT IDENTIFIER 2.5.4.3
    										UTF8String CA
    					[2](1 byte) 01
    

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

    • OBJECT IDENTIFIER 2.5.29.35 — набор цифр описывает какая информация хранится в блоке. Последовательность 2.5.29.35 означает, что перед нами информация о ключе подписанта.
    • [0](20 byte) 6FBC9476035CB50061524C4ABE9064C9C4C32E6B — идентификатор ключа издателя. SHA-1 хеш от закодированного с помощью DER открытого ключа.
    • OBJECT IDENTIFIER 2.5.4.3 — определяет, что следующее поле представляет имя издателя.
    • [2](1 byte) 01 — серийный номер сертификата издателя.

    Напоследок откроем полученный сертификат с помощью стандартных средств и убедимся, что все необходимые данные на месте:
    Имя сертификата	User
    Издатель	CA
    Версия сертификата	3
    Серийный номер	0x2
    Недействителен до...	15.09.2013 15:35:00 GMT
    Недействителен после...	22.09.2113 15:35:00 GMT
    Цифровая подпись (SHA-1)	A4 E7 9B AD E7 E2 67 B1 8A D6 6F F9 61 0D 42 A9 DB C3 A9 67
    Цифровая подпись (SHA-256)	39 A2 D8 47 CE F7 E7 C3 81 62 8A 4A 65 F3 4A E8 6F 12 B7 8A 1A ED F9 94 6E 57 19 F6 39 DA B7 8F
    Открытый ключ(1024 битный)		Алгоритм открытого ключа	rsaEncryption
    Модуль	00: A7 BA 25 52 5F 0D 82 EE 2C B1 F0 E1 E2 0D 3F B2
    10: 25 06 DB A2 5A B0 D3 00 D7 2C 1F 85 8C 71 73 95
    20: 8A 06 6C 04 6D 4B AB 15 50 1E 53 92 9F BA 6E 04
    30: 5D 71 6B C0 0A 8E 6C 51 51 2F 27 2E BB 8E C9 FF
    40: 9C C2 E2 45 56 26 6B 61 C5 C1 67 0C 6F A9 8A 16
    50: 76 8E 12 DB 38 A2 B3 09 6F B3 39 DD 9B EB 98 B7
    60: 61 9F 9E 18 65 4F DB AB 74 72 79 AC 14 7C 24 D8
    70: 47 16 5B 17 30 CB 6E FB 45 5E D1 04 37 FA 85 C3
    Экспонента	01 00 01                                       
    
    Подпись		Алгоритм подписи	sha1WithRSAEncryption
    Подпись	00: 2C 1C D9 7F B4 F2 D0 10 16 7A B7 29 D6 89 A4 A1
    10: 2B 4A 78 1B 85 38 53 83 4E 71 3C 81 C0 A5 AD A8
    20: AB 16 59 F4 D3 A7 7E 83 2F AE 21 75 9E 91 F6 FC
    30: 93 A3 AE F5 27 CF 5F 0B C9 5F DC E1 75 26 D5 39
    40: 74 32 39 B9 BD 95 79 A7 EE 02 0C 56 0A A9 A5 83
    50: F8 86 0D 6F B5 7F C5 FE 23 0B 4B 5C 65 A8 BC 89
    60: 36 37 B3 53 74 BB 25 66 10 F8 53 AA EF 05 9E ED
    70: 74 04 E9 3D F4 DF 85 71 37 57 5D E7 D8 C6 8E EA
    
    Расширения		X509v3 Authority Key Identifier	keyid:6F:BC:94:76:03:5C:B5:00:61:52:4C:4A:BE:90:64:C9:C4:C3:2E:6B DirName:/CN=CA serial:01
    


    Заключение


    Тех усидчивых людей, которые продрались сквозь все эти ASN.1 выражения и шестнадцатеричные наборы данных, я хотел бы поблагодарить за прочтение. Надеюсь вам было хоть немного интересно. И стало чуточку понятнее, что же такое на самом деле X.509 сертификат.

    Ну и как всегда немного ссылок для тех, кому хочется больше подробностей.
    1. RFC5280 — спецификация x.509 сертификата и списка отзывов сертификатов.
    2. Руководство по выживанию — SSL/TLS и сертификаты X.509
    3. ASN.1 простыми словами, вариант статьи для хабра
    4. on-line утилита для декодирования DER-файлов
    5. Первичный стандарт ITU-T X.509 (+ русский перевод). Спасибо ystr за ссылку.
    Поделиться публикацией

    Комментарии 31

      –6
      Достаточно интересно. Спасибо.
        –5
        Очень полезная инфа, благодарю.
          +5
          Моя статья есть и на Хабре: ASN.1 простыми словами.

          Однако должен сказать, что Ваша статья IMHO достаточно странна. Либо она про X.509 сертификаты и тогда она очень поверхностна, либо она про некий пример ASN.1 структур на примере разбора X.509 сертификата. Но тогда для меня она тоже поверхностна.
            +1
            Знаю, читал. Очень помогла кстати. Спасибо.
            Теперь про ваш вопрос, или это не вопрос?) Я бы не стал называть это громким словом «статья», обычный пост в блоге. Было интересно поковырять собственноручно сгенерированный сертификат, нашел пару прикольных вещей, описал процесс в коллективном блоге.
            Кому-то это покажется поверхностным, а кому-то (таким людям типа меня, которым было просто любопытно узнать что же это за зверь такой x.509) поможет удовлетворить любопытство.
            Конечно, есть еще очень много всего, что в топик просто не уместилось. Если кому-то станут интересны подробности постараюсь ответить в комментариях. А не получится у меня, помогут умные люди типа вас, за это мы хабр и любим.)
            0
            Вот эту фразу не понял:
            В TSB-сертификат входит последовательность SEQUENCE второго уровня со всеми вложенными данными.
            Не могли бы вы это пояснить (и выделить в примере подписываемую часть болдом).
              0
              Смотрите, вот наш сертификат целиком:
              Сертификат
              SEQUENCE(3 elem)
                  <TBS>SEQUENCE(7 elem)
                      [0](1 elem)
                          INTEGER 2
                      INTEGER 1
                      SEQUENCE(2 elem)
                          OBJECT IDENTIFIER 1.2.840.113549.1.1.5
                          NULL
                      SEQUENCE(1 elem)
                          SET(1 elem)
                              SEQUENCE(2 elem)
                                  OBJECT IDENTIFIER 2.5.4.3
                                  UTF8String CA
                      SEQUENCE(2 elem)
                          UTCTime 13-09-15 15:35:02 UTC
                          GeneralizedTime 2113-09-22 15:35:02 UTC
                      SEQUENCE(1 elem)
                          SET(1 elem)
                              SEQUENCE(2 elem)
                                  OBJECT IDENTIFIER 2.5.4.3
                                  UTF8String CA
                      SEQUENCE(2 elem)
                          SEQUENCE(2 elem)
                              OBJECT IDENTIFIER 1.2.840.113549.1.1.1
                              NULL
                          BIT STRING(1 elem)
                              SEQUENCE(2 elem)
                                  INTEGER 00: 8D 80 B5 8E 80 8E 94 D1 04 03 6A 45 1A 54 5E 7E
                                              EE 6D 0C CB 0B 82 03 F1 7D C9 6F ED 52 02 B2 08
                                              C3 48 D1 24 70 C3 50 C2 1C 40 BC B5 9D F8 E8 A8
                                              41 16 7B 0B 34 1F 27 8D 32 2D 38 BA 18 A5 31 A9
                                              E3 15 20 3D E4 0A DC D8 CD 42 B0 E3 66 53 85 21
                                              7C 90 13 E9 F9 C9 26 5A F3 FF 8C A8 92 25 CD 23
                                              08 69 F4 A2 F8 7B BF CD 45 E8 19 33 F1 AA E0 2B
                                              92 31 22 34 60 27 2E D7 56 04 8B 1B 59 64 77 5F
                                  INTEGER 65537</TBS>
                      SEQUENCE(2 elem)
                          OBJECT IDENTIFIER 1.2.840.113549.1.1.5
                          NULL
                  BIT STRING 00: 0A 1C ED 77 F4 79 D5 EC 73 51 32 25 09 61 F7 00
                                 C4 64 74 29 86 5B 67 F2 3D A9 39 34 6B 3C A9 92
                                 B8 BF 07 13 0B A0 9B DF 41 E2 8A F6 D3 17 53 E1
                                 BA 7F C0 D0 BC 10 B7 9B 63 4F 06 D0 7B AC C6 FB
                                 CE 95 F7 8A 72 AA 10 EA B0 D1 6D 74 69 5E 20 68
                                 5D 1A 66 28 C5 59 33 43 DB EE DA 00 80 99 5E DD
                                 17 AC 43 36 1E D0 5B 06 0F 8C 6C 82 D3 BB 3E 2B
                                 A5 F1 94 FB 53 7B B0 54 22 6F F6 4C 18 1B 72 1C
              


              Отступы в тексте описывают уровень вложения, т.е. SEQUENCE(3 elem) первый уровень вложения, все элементы сертификата входят в этот блок.
              SEQUENCE(7 elem) — второй уровень вложения, и т.д.
              Собственно элементы этого самого SEQUENCE(7 elem) и будут входить в TSB-сертификат. В блоке <source> тэги не работают, выделил часть TBS-сертификата «тэгом» <TBS>.
                +2
                Для лучшего понимания (всеми участниками) следует сказать, что аббревиатура «TBS» расшифровывается как «To Be Signed». То есть фактически сертификат состоит только из трех полей: части «to be signed», идентификатора алгоритма подписи и собственно самой подписи. Следует трактовать «TBS certificate» как некий набор элементов, который удостоверяются подписью этого сертификата. TBS могло бы быть просто куском данных, но для сертификата правильнее иметь в TBS части имена выдающей и принимающих сторон, а также дополнительные атрибуты сертификата.
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Здравствуйте.
                По первому пункту по-моему вы не совсем правы, если я вас правильно понял. То что отпечаток может вычисляться не только с помощью SHA-1 это вы верно подметили. А вот, то что алгоритм отпечатся можно задать при генерации этого я нигде не нашел. Насколько понял проверяющая программа сама выбирает хеш-функцию, а так как стандарт по умолчанию долгие годы был SHA-1, во многих реализациях используется именно эта функция.

                А вот по второму пункту тут я с вами целиком согласен. Действительно, если сгенерировать сертификат 2-го уровня и не указать в нем дополнительные сведения об издателе сертификат все-равно найдет правильного издателя по наименованию. Я внес небольшие поправки в пост, чтобы не вводить читателей в заблуждение.
                Я просто хотел немного вас дополнить и сказать, что такого рода поля, содержащие, например, сведения об издателеи называются расширениями. И возможность добавлять расширения доступна только для сертификатов версии 3.
                +1
                Отвечу сразу на предыдущие два комментария.

                Насчет отпечатка в SHA-1: он действительно должен быть только в SHA-1. Сделано это только для того, чтобы все без исключения программы смогли однозначно идентифицировать данный сертификат. То есть например в Windows сертификаты находятся в так называемых «хранилищах сертификатов» и чтобы быстро найти нужный частно прибегают к поиску по хэшу сертификату, который и вычисляется только с помощью SHA-1. И так как этот отпечаток нужен исключительно только для различения сертификатов у локального пользователя то совершенно все равно скомпрометирован ли алгоритм SHA-1 или нет — это просто «отпечаток», простой уникальный номер сертификата.

                Насчет различия самоподписанных и не самоподписанных сертификатов. По сути этих различий нет никаких. То есть также есть только три части сертификата (TBS, алгоритм подписи и сама подпись), отличается только на каком закрытом ключе выполнена подпись сертификата. В случае самоподписанного сертификата подпись выполнена на закрытом ключе соответствующему открытому ключу (который находится в TBS-части). В случае же не самоподписанного сертификата подпись выполнена на закрытом ключе «удостоверяющего центра», который как-бы удостоверивает, что этот публичный ключ (упомянутый в TBS-части) действительно соответствует данному лицу. Конечно различия самоподписанного и не самоподписанного сертификата на это не кончаются (например в самоподписанном сертификате поля «Issuer» и «Subject» в TBS-части совпадают, на в не самоподписанном — всегда различны), но более рассмотрение более детальных различий приведет к очень раздутому комментарию.

                Также следует упомянуть, что автор рассматривает не первичный стандарт ITU-T X.509, а «производный» стандарт RFC5280. Кстати было бы здорово дополнить статью ссылкой на действительный стандарт X.509 (тем более что он уже полностью переведен на русский язык, как минимум для версии стандарта от 2005-го года): ITU-T X.509 (+ русский перевод).
                  0
                  Не видел этот документ. Спасибо за ссылку. Добавил в пост.
                    0
                    Это первичный стандарт и он изначален. Однако все программные реализации опираются на RFC3280 (или RFC5280), так что этот первичный стардарт только вспомогателен. А ссылка полезна прежде всего тем, что есть русский перевод всего стандарта.
                    0
                    Сертификаты на SHA-1 перестают распознаваться MS как валидные с 2016 года.
                    sha1-deprecation-policy
                    +2
                    Поэтому, в целях экономии места, а также более удобной обработки хранимой в ASN.1-форме информации, был разработан специальный метод кодирования — DER.

                    DER-кодировка описывается следующим правилом. Первым записывается байт, характеризующий тип данных, затем последовательность байтов хранящих сведения о длине данных и затем уже записываются сами данные.

                    Вот это вот в корне ошибочно.
                    На самом деле сам по себе стандарт ASN.1 содержит в себе как язык описания схем, так и правила кодирования. Правила кодирования действительно достаточно просты: все значения записываются в виде структур «TLV» (tag-length-value). Базовые требования к кодированию ASN.1 типов определяются в BER (Basic Encoding Rules). Именно эти правила являются основными для ASN.1. То что называется DER (Distinguished encoding rules) определяет не новые правила, а всего лишь накладывает ограничения на BER. Изначально DER был сделан из-за того, что с помощью BER один и тот же элемент данных мог быть закодирован различными способами. С помощью DER добавили ограничения и теперь каждый тип данных может быть закодироват одним способом В ограничения описываемые в DER входят:
                    1) Следует использовать явную форму кодирования длины структур данных;
                    2) Нельзя использовать конструктивную форму кодирования для типов BITSTRING, OCTETSTRING и для други строковых типов;
                    + дополнительный набор правил-ограничений.

                    Так что DER — это не кодировка, а просто набор ограничений, наложенный на базовые правила кодирования (BER). Конечно я повсеместно вижу, как употребляют выражения вида «в DER-кодировке», и уже почти бросил обращать на это внимание, но все-таки заострю этот момент в данной статье.
                      0
                      Я так понял есть еще некий вариант CER. Не могли бы вы пояснить что это такое и чем он отличается от der?
                        +1
                        CER (Canonical encoding rules) — несколько меньшие ограничения, чем в DER. Лучше бы сразу ознакомится со стандартом X.690 (основной современный стандарт ASN.1), там всё очень подробно расписано.
                          0
                          Понятно.
                          Спасибо большое за уточнения и разъяснения.
                      0
                      Спасибо, для общей информации мне хватает сведений из этой статьи. Как раз недавно впервые вплотную столкнулся с сертификатами. Но проблемой было не понимание содержимого сертификатов, а вообще сама их генерация и назначение. Все эти PEM, DER, base_64 и прочие. Неплохо бы обзорную статью о всяких видах и целях тех или иных ключей.
                      В частности, когда мне понадобилось вытащить приватный ключ из JavaKeyStore, пришлось неплохо покурить гугл, но в итоге таки вытащил его.
                        +1
                        Неплохо бы обзорную статью о всяких видах и целях тех или иных ключей.

                        Не уловил о чем вы. Вы имеете в виду назначение сертификата? Например: шифрование файлов или электронная почта. Или вы о чем то другом?
                          0
                          Я о том, что один и тот же сертификат можно получить в DER-формате, в DER-Base64 формате, в PEM формате и ещё в бог весть скольки форматах. При чем визуально, DER-base64 и PEM очень похожи. Учитывая, что расширение у файла может быть каким угодно — вообще не очень ясно, как определить формат сертификата. И вопрос в том — какое назначение всех этих вариантов? А ещё есть выгрузка из keystore в формате PEM, но с такой вот странной конструкцией:
                          Bag Attributes
                          friendlyName: sso
                          localKeyID: 54 69 6D 65 20 31 33 37 39 32 35 39 30 35 37 39 38 30
                          Key Attributes: -----BEGIN RSA PRIVATE KEY-----

                          -----END RSA PRIVATE KEY-----
                          Bag Attributes
                          friendlyName: CN=Pravo Ru,OU=Pravo.ru,O=Pravo.ru,L=Moscow,ST=Moscow,C=RU
                          localKeyID: 54 69 6D 65 20 31 33 37 39 32 35 39 30 35 37 39 38 30
                          subject=/C=RU/ST=Moscow/L=Moscow/O=Pravo.ru/OU=Pravo.ru/CN=Pravo Ru
                          issuer=/C=RU/ST=Moscow/L=Moscow/O=Pravo.ru/OU=Pravo.ru/CN=Pravo Ru
                          -----BEGIN CERTIFICATE-----

                          -----END CERTIFICATE-----

                          Из этого странного файла я руками вырезал private key, чтобы сохранить его в PEM, конвертировать в DER и отдать своей системе для подписывания запросов.
                            0
                            Ну что касается PEM, тот тут все просто объясняется. Это действительно обычный DER сертификат в base64 кодировке.:)
                            Что касается использования, могу предположить, что PEM вероятно применяется в электронной почте, т.к. DER сертификат содержит бинарные данные и его нельзя отправить в теле письма.
                            А вот второй формат выгрузки действительно любопытен. Не встречался с такой формой записи.
                              0
                              Тем не менее, выгруженный DER в формате base64 у меня отличается от выгруженного PEM-сертификата. Как так может быть?
                                0
                                Странно. Какими средствами пользуетесь? Только что проверил в браузере, выгрузил DER сертификат и PEM. Конвертнул DER в base64, все совпало. Но я на этом не успокоился и из хранилища сертификатов windows выгрузил DER сертификат в base64, тоже самое значение получилось.
                                  0
                                  Точно не вспомню, но по-моему было так:
                                  keytool -export -alias SSO -keystore keystore -rfc -file certificate.pem

                                  Против виндосовского «копировать в файл» > «Файлы X.509 (.CER) в кодировке Base64.
                                  В результате получил два файла с разным содержимым.
                              +1
                              Стандарт X.509 определяет, что основная кодировка для сертификата — ASN.1 DER. Стандарт PEM (Privacy Enhancement for Internet Electronic Mail) был изначально придуман для передачи бинарных сообщений в составе S/MIME сообщений (то есть для передачи бинарных сообщений в обычной электронной почте). Действительно PEM — это простое применение BAS64 кодирования к начальному бинарному сообщению.

                              Для бОльшего знания в этом вопросе можно почитать стандарты RFC 1421-1424.
                                0
                                Точнее сначала был PEM, а сейчас S/MIME.
                          0
                          — комментарий удалён
                            +2
                            Печально, что отсутствует упоминание инструмента openssl, а там, кстати говоря, есть целый блок команд для работы с x509. IMHO, это было бы лучше, чем пример на .net.
                              –1
                              А лучше взять и выбросить OpenSSL и написать что-то новое и нормальное.
                                0
                                «Проминусовал, залез посмотрел исходники openssl»
                              0
                              Ещё полезная ссылка:
                              OBJECT IDENTIFIER 1.2.840.113549.1.1.5
                              Что значит подобная последовательность чисел (OID) можно посмотреть тут oid-info.com/cgi-bin/display?tree

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

                              Самое читаемое