ASN.1编解码标准X.690
书接上文,我们有提到X.509
格式的证书通常是使用ASN.1
的格式编码的。那么ASN.1
是个啥,如何进行编码解码呢。这篇文章主要用来解读ASN.1
的标准,学习成为解码人柱力。
ASN.1与TLV
先看看wiki对于ANS.1
格式的描述:
在电信和计算机网络领域,ASN.1(Abstract Syntax Notation One) 是一套标准,是描述数据的表示、编码、传输、解码的灵活的记法。它提供了一套正式、无歧义和精确的规则以描述独立于特定计算机硬件的对象结构。
看起来很厉害,再看看其类别有哪些:
ASN.1本身只定义了表示信息的抽象句法,但是没有限定其编码的方法。各种ASN.1编码规则提供了由ASN.1描述其抽象句法的数据的值的传送语法(具体表达)。标准的ASN.1编码规则有基本编码规则(BER,Basic Encoding Rules)、规范编码规则(CER,Canonical Encoding Rules)、唯一编码规则(DER,Distinguished Encoding Rules)、压缩编码规则(PER,Packed Encoding Rules)XML编码规则(XER,XML Encoding Rules)。
很显然,这种编码方法非常多。但是不要慌,大部分密码相关标准用到的编码方式都是BER/CER/DER
,而后面两种则是对BER
增加限制后的产物。至于PER/XER
,至少在笔者目前工作中还没有接触到过,本文就不进行介绍了。
为什么要把前三者归为一类呢,原因很简单,这三种编码方式是一种典型TLV
的编码方法。TLV:Type-Length-Value
, 是一种各类通讯协议中都非常常见的编码手段,他将一段数据分解使用Type
描述了数据类型,用Length
描述的数据长度,最后的Value
表示了真正的data
。其优点也非常明显,他可以将一段数据以二进制的格式编码,大量压缩了编码导致的报文体积膨胀的消耗,同时由于编码简单,解析速度也非常迅速。其结构很简单,通常如下:
Type | Length | Value
以一个手机号码的编码为例18570917612
:
先看Type
,假设用一个字节表示Type
,如0x00
表示手机号码,0x01
表示固定电话号码,0x02
表示传真号等等。当然此时还可以区分国家号、区号等等,都可以通过Type
进行拓展,这里我们简单只考虑号码的类型。那显然可以编码为0x00
, 编码的类型通过也称为Tag
。
Length
就比较简单,通常标识字节数,而手机号码通常是使用ASCII
码来标识,则每一个手机号码数字被编码为一个字节,总计就是11
,长度被编码为0x0B
.
最后Value
就是数据主体,使用ASCII
编码则为:0x31 0x38 0x35 0x37 0x30 0x39 0x31 0x37 0x36 0x31 0x32
最终这个手机号码将被编码为(去掉0x,直接看真实的二进制下表示):
Tag | Length | Value |
---|---|---|
00 | 0B | 31 38 35 37 30 39 31 37 36 31 32 |
当然,TLV
是支持嵌套的,即Value
同样是一个TLV
编码的数据。后面也将会看到。
X.690
既然ASN.1
是一个通用的,和通信与密码强相关的编码记法,当然是需要一个明确的标准。而X.690
是当前比较公认的标准。主要也是定义了BER/CER/DER
这三种编码格式。下文的解析也是遵循该标准的(08/2015)
版本 。
BER
作为基础编码规则,我们首先学习它。BER
通常把一个字节叫做Octets
,而从低到高的比特位分别被叫做bit1 - bit8
,和通常计算机对于最低比特称作bit0
略有不同。
先贴一下之前的X.509
证书, 用于后文实例解析中的对比:
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
bc:01:41:05:22:d8:cc:7f:02:00:00:00:00:79:64:7f
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, O = Google Trust Services, CN = GTS CA 1O1
Validity
Not Before: Aug 26 08:14:23 2020 GMT
Not After : Nov 18 08:14:23 2020 GMT
Subject: C = US, ST = California, L = Mountain View, O = Google LLC, CN = www.google.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:bc:1c:aa:96:6f:6f:99:48:79:56:61:4b:7f:ff:
dc:39:08:3a:d4:4d:e2:d8:87:80:af:3d:18:5e:71:
2d:ce:09:70:57:39:38:5f:2a:ee:a8:35:f4:3a:86:
86:5a:1d:c7:31:32:1b:8d:ac:d0:46:ad:c3:fc:a5:
d3:18:36:68:ab
ASN1 OID: prime256v1
NIST CURVE: P-256
X509v3 extensions:
X509v3 Key Usage: critical
Digital Signature
X509v3 Extended Key Usage:
TLS Web Server Authentication
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Subject Key Identifier:
AF:32:A8:9D:20:98:F3:FD:14:41:FE:F4:C4:74:47:7C:D1:6C:81:B1
X509v3 Authority Key Identifier:
keyid:98:D1:F8:6E:10:EB:CF:9B:EC:60:9F:18:90:1B:A0:EB:7D:09:FD:2B
Authority Information Access:
OCSP - URI:http://ocsp.pki.goog/gts1o1core
CA Issuers - URI:http://pki.goog/gsr2/GTS1O1.crt
X509v3 Subject Alternative Name:
DNS:www.google.com
X509v3 Certificate Policies:
Policy: 2.23.140.1.2.2
Policy: 1.3.6.1.4.1.11129.2.5.3
X509v3 CRL Distribution Points:
Full Name:
URI:http://crl.pki.goog/GTS1O1core.crl
CT Precertificate SCTs:
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 5E:A7:73:F9:DF:56:C0:E7:B5:36:48:7D:D0:49:E0:32:
7A:91:9A:0C:84:A1:12:12:84:18:75:96:81:71:45:58
Timestamp : Aug 26 09:14:24.417 2020 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:20:77:F3:D6:8B:51:4F:88:71:16:73:ED:36:
2F:64:F4:77:3E:92:D3:CE:97:1F:1C:53:FA:4E:FB:5B:
D7:0A:4C:D6:02:21:00:9F:B9:FE:F1:F3:1C:0D:CF:20:
30:B1:1C:0A:01:65:AD:67:90:1F:B5:33:90:8D:49:4D:
2B:ED:1D:90:28:A1:6B
Signed Certificate Timestamp:
Version : v1 (0x0)
Log ID : 07:B7:5C:1B:E5:7D:68:FF:F1:B0:C6:1D:23:15:C7:BA:
E6:57:7C:57:94:B7:6A:EE:BC:61:3A:1A:69:D3:A2:1C
Timestamp : Aug 26 09:14:24.367 2020 GMT
Extensions: none
Signature : ecdsa-with-SHA256
30:45:02:21:00:F4:67:8E:8B:ED:3F:B2:D4:EA:72:EB:
53:F1:52:57:98:D6:63:0E:C0:6B:68:46:CE:F3:AD:25:
52:AD:12:83:27:02:20:05:CA:04:76:D6:4F:2A:E5:D3:
96:85:79:A2:F3:85:29:9E:89:30:00:A7:20:99:2D:F7:
C9:56:3C:4E:5D:5C:CF
Signature Algorithm: sha256WithRSAEncryption
7a:9a:76:80:c9:39:13:8e:60:b1:93:5d:99:49:1b:71:b5:b2:
2e:bd:4b:db:56:f0:eb:fa:f4:ae:93:f6:1b:dd:b0:df:2a:81:
08:fc:4a:a9:ec:b1:ae:09:f0:fa:40:7b:b8:be:dc:08:4c:46:
32:99:29:f8:13:6b:72:af:16:79:63:d3:3f:76:56:57:19:78:
91:86:f8:7a:ee:26:67:98:dc:5e:e4:00:f5:87:a0:01:21:9d:
cf:e5:9f:02:f3:2a:fd:0e:fd:78:af:2e:20:29:77:35:e2:c6:
30:ee:ef:be:f2:bb:26:02:52:a2:2d:27:78:ce:a9:8e:39:d0:
a2:74:90:11:c5:92:58:3c:7a:88:1d:c7:5a:56:d4:1a:01:00:
c3:9d:98:6f:41:02:1f:cb:e2:4d:99:6a:5c:d9:0f:c0:88:08:
15:c5:26:90:a2:a4:15:f6:71:e2:fe:a9:98:dc:40:2a:71:c1:
11:aa:00:73:52:24:74:aa:ae:72:55:2f:0d:31:b7:00:bb:1f:
87:4d:f5:05:ad:ff:7a:93:e0:cf:86:a5:1d:1b:7d:41:fa:10:
99:3b:00:7c:c9:dd:a9:52:5c:06:72:86:96:e7:05:97:77:12:
2f:26:bb:dc:65:c4:48:4d:9c:82:4b:7d:69:27:3f:85:00:2e:
b1:5d:8d:dc
Identifier Octets
标识字节,可以理解为TLV
中的Type
。BER
中将其分解成三段如下:
* Bit 8 7 6 5 1
* +-------+-----+------------+
* | Class | P/C | Tag number |
* +-------+-----+------------+
先看Class
段,两个比特bit7-bit8
,定义了四种类型:
Class | Bit 8 | Bit 7 |
---|---|---|
Universal (0x00) | 0 | 0 |
Application (0x40) | 0 | 1 |
Context-specific (0x80) | 1 | 0 |
Private (0xC0) | 1 | 1 |
其中Universal
是最常用的,属于Native
的类型都将属于该段。而Context-specific
也比较多见,密码学相关编码较多。通常当字段的类型非标准类型时都将使用该类型作为Identifier Octets
的高位。以X.509
证书中标准ASN.1
版本号为例,Type
被描述为A0
:
Version ::= INTEGER { v1(0), v2(1), v3(2) }
version [0] EXPLICIT Version DEFAULT v1,
/* Tag is 'A0': 0x80 | 0x20 | 0 */
此时A0
的高位比特即为1和0,对应Context-specific
。Context-specific
它只能使用在SEQUENCE
、SET
和CHOICE
类型的组件中,而且可以看到有一个关键字EXPLICIT
以及对应的关键字IMPLICIT
用于配合使用。而中括号中间的0
则标识了对应的Tag number
, 注意该数字仅在该上下文中(即单独SEQUENCE
、SET
和CHOICE
组成的内部)生效。
其他两种使用很少,也不推荐使用,多用于某些应用和公司内部的私有协议,此处不讨论了。
再看看P/C
, 对应1个比特:0 = Primitive(0x00)
; 1 = Constructed(0x20)
。通常该位标识后续的Value
是一个原子值还是一个嵌套的TLV
. 例如INTEGER
只能为Primitive
,而SEQUENCE
一定是Constructed
;当然也有Both
允许的类型,如BIT STRING
,可能是嵌套的也可能是原子的,不过显然嵌套的情况比较少,X.509
证书暂时没找到相关例子。
最后是最关键的Tag Number
,使用了5个比特(bit1 - bit5)
标识了最基本的原生类型,下表将解释常用的类型:
Name | Permitted Construction | Decimal Tag number | Hexadecimal Tag number |
---|---|---|---|
End-of-Content (EOC) | Primitive | 0 | 0 |
BOOLEAN | Primitive | 1 | 1 |
INTEGER | Primitive | 2 | 2 |
BIT STRING | Both | 3 | 3 |
OCTET STRING | Both | 4 | 4 |
NULL | Primitive | 5 | 5 |
OBJECT IDENTIFIER | Primitive | 6 | 6 |
UTF8String | Both | 12 | C |
SEQUENCE and SEQUENCE OF | Constructed | 16 | 10 |
SET and SET OF | Constructed | 17 | 11 |
PrintableString | Both | 19 | 13 |
T61String | Both | 20 | 14 |
IA5String | Both | 22 | 16 |
UTCTime | Both | 23 | 17 |
GeneralizedTime | Both | 24 | 18 |
UniversalString | Both | 28 | 1C |
BMPString | Both | 30 | 1E |
具体的在X.509
所需的编码字段将在实例中进行讲解。需要特别提到的EOC
通常用在TLV
串结尾标识数据已结束,属于一种可选编码方式,X.509
中未使用。
一个问题在于,此时Tag Number
是无法超过0x0F
的,而根据X.690
标准实际上是有超过0x0F
种Tag
的,此时该如何编码呢?ASN.1
使用0b11111
作为Tag Number
进行拓展,此时接下来的字节为真实的Tag Number
,其中当最高位比特bit8
为标识位,0
标识为最后一个字节,1标识为中间字节,最后进行比特串的拼接,大端编码。类似下面的标识:
* Leading Octet Bit 8 7 6 5 1
* +-------+-----+---------+
* | Class | P/C | 0b11111 |
* +-------+-----+---------+
* 2nd Octet Bit 8 7 1
* +---+------------+
* | 1 | 0bxxxxxxx |
* +---+-----+------+
...
* Last Octet Bit 8 7 1
* +---+------------+
* | 0 | 0bxxxxxxx |
* +---+-----+------+
* Example for tag number 0x80: 0b10000000
* binary : [class:P/C:11111][1:0000001][0:0000000] for 3-byte
不过这种情况比较少见,在密码学标准中基本不会出现。
Length octets
标识了编码内容的长度,属于比较简单的字段。与Tag Number
编码方式类似,也被区分为了短模式与长模式:
短模式下,bit8
将为0
,剩下的比特将作为实际数据长度。例如上面的手机号码长度11
,将被编码为0b00001011
。显然这种情况下长度是有限的:不能超过127(0b01111111)
个byte.
超过127之后将使用长模式:第一个字节的最高比特bit8
置1
,剩下的比特将指示该长度将由多少个字节来表示,同样的将通过大端编码的方式形成一串比特串进行编码。以上一篇X.509
的证书签名段为例:
/*
* Type 0x03 : Bit String
* Length:
* 1st Octet: 0b10000002(0x82): bit-8 = 1 stands for long form;
* bit1-7 = 0x2 encode the number of subsequent octets in the length octets, bit-7 is the most significant bit; Note: 0xFF can't be used.
* 2nd and 3rd Octet: bit 8 of the first subsequent octet is the most significant bit;
* 0b00000001|00000001(0x01 0x01) for content length 0x101.
*/
signatureValue BIT STRING
(Encoded as: [03] [82 01 01] [Signature])
这里有个疑问:为啥RSA-2048
为啥出来需要0x101
个字节,多了一个字节啊。其实看一下就知道,编码的最高字节为0x00
。至于这个的用法如何将在后续对不同Type
的内容做编码时讲到。
此外ASN.1
提供了另外一种灵活的编码方式:即当Length
被编码为0b10000000(0x80)
时,长度被定义为不定模式(Indefinite)
,后续Contents octets
的尾部则必须加上上节提到的EOC
,标识内容结束。
Contents octets
编码内容,这个部分将对X.509
系列所需常用的类型编码方式进行详述
EOC
EOC
模式一定为以下格式:
Identifier | Length | Contents |
---|---|---|
0x00 | 0x00 | NULL |
Boolean
布尔类型非常简单,Type
的Class
为Universal
,非嵌套,Tag number
为0x1
, Length
为0x1
。当Value
字节为全0
时记为false
, 否则为true
;下表表示一种布尔类型的编码,共3字节。
Identifier | Length | Contents |
---|---|---|
0x01 | 0x01 | 0xFF |
X.509
中有一个标准的拓展字段用了该布尔类型:
/* X509v3 Basic Constraints: critical
CA:FALSE */
/* encode: */
[01] [01] [FF]
Integer
Type
的Class
为Universal
,非嵌套,Tag number
为0x02
, Length
为编码的字节长度,content
为大数编码的整数,即第一个字节的最高比特为MSB
,直接以上一章证书中间的serialNumber
, 很简单,标识了一个大数88比特、11字节的大数:
CertificateSerialNumber ::= INTEGER
serialNumber CertificateSerialNumber
/* Serial Number:
bc:01:41:05:22:d8:cc:7f:02:00:00:00:00:79:64:7f */
[02] [11] [00 BC 01 41 05 22 D8 CC 7F 02 00 00 00 00 79 64 7F]
其中数字的比较与普通数字比较没有差别,如上述数字与[02] [10] [BC 01 41 05 22 D8 CC 7F 02 00 00 00 00 79 64 7F]
比较是相等的。
Bitstring
Type
的Class
为Universal
,可以为primitive
也可以为constructed
. Tag number
为0x3
. Length
为编码的字节长度;其content
的组成,第一个字节将编码了最后一个字节中填充的0bit的个数, 故解码时也应该去除掉最后一个自己的对应比特数的0,该字节被称为Unused bits
;通常可以使用多个primitive
的Bitstring
分段,组成一个constructed
的Bitstring
, 此时Length
可以设置为不定长模式0x80
. 通常不需要使用construct
模式。看下面的例子:
/* If of type BIT STRING, the value '0A3B5F291CD'H */
/* Primitive mode encode: 04 stands for the last byte include 4 bit padding */
[03] [07] [[04] 0A 3B 5F 29 1C D0]
/*
* Constructed mode encode: 23 stands for 0x00(class) | 0x20(constructed) | 0x03(Tag number)
* The Bitstring consists of 2 sub-bitstrings: 0A3B, first byte 00 stands for 0 padding bits;
* And 5F291CD0, first byte 04 last byte include 4 bit padding.
*/
[23] [0C] [[[03] [03] [[00] 0A 3B]] [[03] [05] [[04] 5F 29 1C D0]]]
解释一下X.509里签名段的编码:
signatureValue BIT STRING
/*
* Signature Algorithm: sha256WithRSAEncryption
* 7a:9a:76:80:c9:39:13:8e:60:b1:93:5d:99:49:1b:71:b5:b2:
* 2e:bd:4b:db:56:f0:eb:fa:f4:ae:93:f6:1b:dd:b0:df:2a:81:
* 08:fc:4a:a9:ec:b1:ae:09:f0:fa:40:7b:b8:be:dc:08:4c:46:
* ...
*/
/* The first byte 00 stands for 0 padding bits */
Encoded as: [03] [82 01 01] [[00] 7A 9A 76 80 C9 39 13 8E 60 B1 93 5D ...]
Octetstring
与Bitstring
基本类似,差别在于Tag number
为0x3
,且因为最小以字节为单位,无需第一个字节标识填充信息。通常在X.509
使用在Extension
中,标准下ASN.1的extnValue格式为Octetstring
,同时由于此类型支持其他类型的嵌套,以之前证书authorityKeyIdentifier
的extnValue
为例:
Extension ::= SEQUENCE {
extnID OBJECT IDENTIFIER,
critical BOOLEAN DEFAULT FALSE,
extnValue OCTET STRING }
/* X509v3 Authority Key Identifier:
keyid:98:D1:F8:6E:10:EB:CF:9B:EC:60:9F:18:90:1B:A0:EB:7D:09:FD:2B */
[04] [18] [30 16 80 14 98 D1 F8 6E 10 EB CF 9B EC 60 9F 18 90 1B A0 EB 7D 09 FD 2B]
NULL
Type
的Class
为Universal
,primitive
. Tag number
为0x5
,Length
一定为0. 不需要Content
,即通常编码固定为05 00
; X.509
中在AlgorithmIdentifier
会出现,如本文的例子中:
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
/* parameters == NULL encoded in {} */
[30] [0D] [[[06] [09] [2A 86 48 86 F7 0D 01 01 0B]] {[05] [00]}]
Sequence/Sequence of
许多使用ASN.1
定义的协议都是基于该类型定义的. Type
的Class
为Universal
, Constructed
, Tag number
为0x10
. 故Identifier
通常为0x30
。通过上一节就能看到,其实整张X.509证书就是一个sequence
,它由 Version
, CertificateSerialNumber
, AlgorithmIdentifier
等多个类型的字段组合而成。例子可以参考上面的AlgorithmIdentifier
;同时可以看一下整张证书的例子:
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING }
/* Cert: length = 0x4C8 */
/*
* TBSCertificate and signatureAlgorithm are also SEQUENCEs with length 0x3B0 and 0x0D
* signatureValue is BIT STRING with length 0x101, bits padding number is 0
*/
[30] [82 04 C8] [[[30] [82 03 B0] [A0 03 02 01 02 02 11 00 ...]]
[[30] [0D] [30 0D 06 09 2A 86 48 86 ...]]
[[03] [82 01 01] [[00] 7A 9A 76 80 C9 39 13 8E 60 B1 93 5D 99 49 1B 71 ...]]]
注意:证书中被签名的字段需要忽略掉Certificate
编码中的Identifier
和Length
,本例中被签名段将忽略掉前两个中括号的内容, 即第三个中括号中的第一个中括号[[30] [82 03 B0] [A0 03 02 01 02 02 11 00 ...]
为被签名段.
Set/Set of
Type
的Class
为Universal
, Constructed
, Tag number
为0x11
. 故Identifier
通常为0x31
。与Sequence
基本一致,区别在于此类型在定义、解析和编码时无需强制顺序,没有前后顺序,各成员体等价。在X.509
中Name
类型包含set
类型.
Name ::= CHOICE { -- only one possibility for now --
rdnSequence RDNSequence }
RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName ::=
SET SIZE (1..MAX) OF AttributeTypeAndValue
AttributeTypeAndValue ::= SEQUENCE {
type AttributeType,
value AttributeValue }
AttributeType ::= OBJECT IDENTIFIER
AttributeValue ::= ANY -- DEFINED BY AttributeType
/* The encoding of a choice value shall be the same as the encoding of a value of the chosen type. */
/*
* Issuer: C = US, O = Google Trust Services, CN = GTS CA 1O1
* Sequence {
* Set {
* Sequence {
* type OBJECT IDENTIFIER, value AttributeValue
* },
* Sequence {
* type OBJECT IDENTIFIER, value AttributeValue
* }, ... },
* Set { ... }
* }
*/
/* Sequence 30 { set 31 { Sequence 30 {OID 06, Printable String 13 }} ... } */
[30] [42] [[[31] [0B] [[30] [09] [[[06] [03] [55 04 06]] [[13] [02] [55 53]]]]] ... ]
UTCTime
Type
的Class
为Universal
, Primitive
和Constructed
都可能, 通常为Primitive
,Tag number
为0x17
。其编码为ascii
码下的YYMMDDhhmm[ss]Z
或YYMMDDhhmm[ss](+|-)hhmm
。
YY
表示年,其中如果YY < 50
则年份为20YY
年,否则为19YY
年,如YY = 50
则标识1950
年;MM
表示月份,DD
表示日;hhmmss
表示时分秒,其中ss
是可选项。
Z
则标识Zulu
时间,而(+|-)hhmm
则标识了与格林威治标准时间的时差, +
标识标准时间提前,-
标识推后。Z
与时差不能共用。
看看X.509
中的例子:
/* Validity
Not Before: Aug 26 08:14:23 2020 GMT
Not After : Nov 18 08:14:23 2020 GMT */
/* ascii code, print as 200826081423Z */
[17] [0D] [32 30 30 38 32 36 30 38 31 34 32 33 5A]
/* ascii code, print as 201118081423Z */
[17] [0D] [32 30 31 31 31 38 30 38 31 34 32 33 5A]
Restricted character string
标识一组收到限制的string
类型,如NumericString
/VisibleString
/PrintableString
等. X.509
最主要使用的是PrintableString
。以Set
例子中的Name
里可打印字符US
为例,identifier = 0x13
:
/*
* PrintableString
* length = 2
* "US" : 0x55 0x53
*/
[13] [02] [55 53]
Object identifier
简称OID
,是一个用来编码特殊意义字段的标准定义ID
,属于ITU-T和ISO/IEC共同开发的一种广泛使用的机制来命名任何类型的对象、概念或事物,具有一个全局明确的名称和一个长生命周期的名称。
Type
的Class
为Universal
, Primitive
, Tag number
为0x6
.
先看该OID
解码的格式,通常为a.b.c.d....
(被称为 dot notation
). 其中每一个.
将分割一个特殊意义的字符。
-
a
中比较典型的有iso(1)
和joint-iso-itu-t(2)
-
b
中比较典型的有member-body(2)
;identified-organization(3)
;ds(5)
;country(16)
-
c
在X.509
典型有certificateExtension(29)
;attributeType(4)
-
d
在X.509
典型有countryName(6)
;organizationName(10)
当然还能延续。其中最典型的OID
是算法ID,以上面证书为例子:sha256WithRSAEncryption
的OID
为1.2.840.113549.1.1.11
; 对应{iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) sha256WithRSAEncryption(11)}
.
具体查询OID
含义可以查看网站http://oid-info.com,可以查到大部分符合标准定义下的OID
的详细描述,以及每个OID
下的child OID
,非常方便。具体实现X.509
功能相关所需的OID
定义可以在下一部分的libmbedtls
源码分析中看到oid.h
中的定义。
其content
编码方式非常特殊:
-
a
和b
将作为第一个字节进行编码,编码结果为:a * 40 + b
。举个例子2.5
的编码结果为0x55 = 85 = 2 * 40 + 5
。 - 剩下的每个字节的编码相同,非常类似超过
30
之后的Tag number
的编码方式:首先被分割为最少数量的没有头零数字的7位数字. 这些数字以big-endian格式进行组织, 并且一个接一个地组合成字节. 编码的最后一个字节j将为0,其他所有字节的最高位(位8)都为1。举个例子840 = 0b1101001000
,编码为0b0000110 | 0b1001000
,最后补充高位标记位0b10000110 | 0b01001000
, 即0x86 0x48
看X.509中对sha256WithRSAEncryption
的编码:
/* 1.2.840.113549.1.1.11 */
/*
* 0x2A = 42 = 1 * 40 + 2: "1.2"
* 840 : 0x86 0x48
* 113549 = 0b (0000110 | 1110111 | 0001101): 0b10000110 0b11110111 0b00001101 = 0x86 0xF7 0x0D
* Remained is simple encode as short integer.
*/
[06] [09] [2A 86 48 86 F7 0D 01 01 0B]
完成基础类型的学习,我们返回去看对于X.509 Version
的编码就好理解了:
version [0] EXPLICIT Version DEFAULT v1,
Version ::= INTEGER { v1(0), v2(1), v3(2) }
/*
* EXPLICIT implies a context-specific(0x80) and construted by a integer;
* [0] stands for the tag number;
* so identifier: 'A0': 0x80 | 0x20 | 0 , length: 0x03, content: type integer;
* Interger tag 0x02, length 0x01, content 0x02;
*/
[A0] [03] [[02] [01] [02]]
完成基础的BER
编码规则的学习之后,CER
和DER
就很好理解了,他们对BER
多加了一些可用性的限制。先看两者共有的与X.509
相关的限制。
Common Restrictions
-
Bool FALSE
所有比特应被编码1,即该字节为0xFF
-
Bitstring
不应该出现需要补充比特的情况,即Unused bits
应该为0 -
Sequence
/Set
中被设置为默认值的成员不应编码 -
UTCTime
仅有Z
模式
CER
-
identifier
为Construted
时,长度应该指定为indefinite
(此处有疑问,.cer
格式下的证书constructed
也有使用definite
长度的) -
identifier
为primitive
时,长度应该指定为最短字节:例如长度0x10
在BER
中可以编码为0x81 0x10
,CER
增加了该限制
DER
- 长度只能使用
definite
模式 -
bitstring
,octetstring
和 restricted character string
不能使用construted
-
Set
中的组成值需与ASN.1
定义的顺序一致
基本上看完此文就可以化身人肉解码器了,最后推荐一个工具:ASN.1 Editor, 很好用。
将着重分析X.509
的编码解码在libmbedtls
中的源码分析,相当硬核。