📜 ⬆️ ⬇️

Parse the x.509 certificate


Hi% username%!

It just so happened that despite a relatively good understanding of the public key infrastructure, the contents of * .crt files always remained a complete mystery to me.
No, don't get it wrong. I know that the x.509 certificate contains information about the owner, the public key, the identification center information, and the electronic digital signature. But when installing the next certificate I was always tormented by curiosity.
What is the difference between a key identifier and a fingerprint? Which certificate data is signed and which is not? And what a data structure allows you to store all this information, reducing redundancy to a minimum.
But finally curiosity overcame laziness, and in this post I will try to describe the structure of x.509 certificates and answer these and other questions.

Part 1. Self-signed certificate


To begin, consider the option of a self-signed root-level certificate.
To simplify the task, we will generate a certificate that will contain only the necessary parameters:

You can do this using the Bouncy Castle library, as follows:
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(); } 


As a result of this procedure, a standard x.509 certificate will be created, which, being opened with a hex editor, looks like this in a wonderful way:
 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 

The same certificate, but already open using standard windows tools:
   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 

Having these two files, one with binary data, and the other with a description of the certificate, try to figure out what's what.
')
First of all, it should be noted that the * .crt file stores information about the certificate in coded form. A special language called ASN.1 is used for encoding.
ASN.1 is a recording standard that describes data structures for representing, encoding, transmitting and decoding data. Wikipedia

With the help of ASN.1 language, it is possible to describe complex structures consisting of data of different types. A typical example of an ASN.1 file looks something like this:
Asn.1 file
 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) 


However, ASN.1 was developed in those bright times when “640 KB should have been enough for everyone” and there was no way to waste space on such a bulky record. Therefore, in order to save space, as well as more convenient processing of information stored in the ASN.1 form, a special coding method was developed - DER.

DER encoding is described by the following rule. The first is the byte that describes the data type, then the byte sequence that stores the data length, and then the data itself is recorded.

For example, the following form is used to encode an integer INTEGER 65537: 02 03 01 00 01.
Here the first byte is 02, it defines the type of INTEGER (you can find the complete type table here, for example), the second byte 03 shows the block length. And the following bytes, 01 00 01, are the hexadecimal notation of our number 65537.

In our case, for the description of the simplest self-signed certificate, 9 data types are sufficient. We give the coding table for these types:

Type NameShort descriptionType View in DER Encoding
SEQUENCEUsed to describe a data structure consisting of various types.thirty
INTEGERInteger.02
OBJECT IDENTIFIERA sequence of integers.06
UTCTimeTemporary type, contains 2 digits to determine the year17
GeneralizedtimeExtended time type, contains 4 digits for the year.18
SETDescribes the structure of data of different types.31
UTF8StringDescribes string data.0C
NullActually NULL05
BIT STRINGA type for storing a bit sequence.03


Knowing how each of these types is encoded, we can try parsing our * .crt file.
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

Transforming the type identifier bytes and removing the bytes describing the length of the blocks we get the following structure:
 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 

This is more similar to what we see when opening certificates in a browser or Windows. Run through each element:


An important point that is particularly worth mentioning is the data for which the signature is calculated. Intuitively, it may seem that all data going to the last BIT STRING field containing the signature is signed. But actually it is not. In the x.509 standard, a certain part of the certificate is signed, called a TBS certificate (to be signed). The TSB-certificate includes a second-level SEQUENCE sequence with all the nested data.
  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 

So if you are faced with the task of verifying the digital signature of an x.509 certificate, then for this you first need to extract the TBS certificate.

Another note relates to the thumbprint of the certificate. As you can see the certificate itself does not contain any information about the fingerprint. This is because the thumbprint is the normal SHA-1 hash value from the entire certificate file, with all its fields, including the publisher's signature. Therefore, it is not necessary to store a fingerprint, you can simply calculate the hash every time you view the certificate.

Part 2. Level 2 Certificate


You and I examined the insides of a self-signed certificate, and it remains for us to understand how the structure of lower-level certificates differs from that of the root center.
To do this, using the secret key of the CA certificate that we have, we will create the user certificate subordinate to it. And this will again help us 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(); } 


After parsing our certificate and converting it to a readable form, we get the following beauty:
 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) 

As you can see, the only difference from the self-signed certificate is the presence of an additional block:
 [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 

which contains information about the issuer of the certificate and its public key. Here I would like to add one comment. Without this block, the certificate will still remain working, since The information stored here is considered to be nothing more than an addition, more precisely indicating which of the publisher's keys the current certificate has been signed. Consider each element of the block separately.


Finally, we will open the received certificate using standard tools and make sure that all the necessary data is in place:
   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 


Conclusion


I would like to thank those diligent people who pushed through all these ASN.1 expressions and hexadecimal data sets. I hope you had a little interesting. And it became a little clearer what the X.509 certificate really is.

Well, as always, few links for those who want more details.
  1. RFC5280 is an x.509 certificate and certificate revision list specification.
  2. Survival Guide - SSL / TLS and X.509 Certificates
  3. ASN.1 in simple terms , a version of the article for Habr
  4. on-line utility to decode DER files
  5. Primary standard ITU-T X.509 (+ Russian translation) . Thanks ystr for the link.

Source: https://habr.com/ru/post/194664/


All Articles