本篇是前一篇 X.690 规范 的延续,主要说明如何解读 ASN.1 抽象语法
一般来说用于 ASN.1 编码的数据结构都会有一个用于描述其编码方式的声明结构,就像 C 语言里面声明一个结构体一样。如下所示是一个 X.509 证书的 ASN.1 声明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
Certificate ::= SEQUENCE {
tbsCertificate TBSCertificate,
signatureAlgorithm AlgorithmIdentifier,
signatureValue BIT STRING
}
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
}
那么上面这一段声明要如何解释呢?
ASN.1 抽象语法
ASN.1 的语法遵循的是巴科斯范式,这是一种形式化的语法表达式,用于描述一种形式体系,或者说抽象结构。巴科斯范式使用如下的定义表示结构:
::=
是左边被定义为右边的意思,比如姓名被定义为一个字符串name ::= String
"word"
双引号引用的字符串表示字符串本身,"
使用double_quote
表示,双引号外边的字符串均有语法含义<>
尖括号包含的是必选项[]
中括号包含的是可选项{}
大括号包含的是可重复 0 至无数次的项|
相当于or
,表示左右两边任选一项
ASN.1 由两部分组成:一部分描述信息内数据,数据类型及序列格式;另一部分描述如何将各部分组成消息。如下面的例子:
1
2
3
4
5
6
7
8
9
10
11
User ::= SEQUENCE {
id OCTET STRING,
name OCTET STRING,
phoneNumber OCTET STRING,
address Address
}
Address ::= SEQUENCE {
province OCTET STRING,
city OCTET STRING,
street OCTET STRING
}
上述的例子中定义了一个 User
的结构体,它由 ASN.1 的 SEQUENCE
结构组成。该结构中包含 id
, name
,phoneNumber
三个字符串信息和 address
这个Address
信息。Address
又由 province
,city
,street
三个字符串信息组成的 SEQUENCE
结构定义。
抽象语法中的 Tag
上一遍 X.690 规范 有说道 ASN.1 的 tag 具有很多的类型,那么这些类型是如何在抽象语法中体现的呢?在证书结构的定义中我们可以看到 issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
这样的数据结构,这里的 [1]
, IMPLICIT
和 OPTIONAL
代表什么呢?
Tag 一共有四种类型:universal
,application
,context-specific
和 private
,这里不推荐使用 application
和 private
。
-
universal 中的 Tag 是 ASN.1 标准定义的,给每一种内建类型定义一个固定 tag 值,具体的 Tag 值可以参考 X.690 规范 中的 ASN.1 原生类型表
-
application 唯一标识应用内的每一个类型,如:
Order-number ::= [APPLICATION 0] NumericString
这里Order-number
的二进制表示为0100 0000
,也就是 Tag Class 为APPLICATION
,TagNumber 为0
,假设NumericString
是Primitive
类型。 -
context-specific 的 Tag 只能出现在
SEQUENCE
,SET
和CHOICE
类型的组件中,如果这些结构类型本身也是另一个结构类型的组成,也可以递归的使用。只要不产生歧义,相同的 tag number可以在不同结构中反复使用。1 2 3 4 5 6 7 8 9 10 11
A-possible-type ::= SET { integer [0] CHOICE { a [0] INTEGER, b [1] INTEGER }, boolean [1] CHOICE { a [0] BOOLEAN, b [1] BOOLEAN } }
- 如果没有明确的写明 Tag Class,那么默认为 Context-Specific。如
[0]
和[CONTEXT-SPECIFIC 0]
是一样的。 - 注意上面的例子中,SET 嵌套了两个 CHOICE,同时两个CHOICE 中都有
a
和b
两个元素,由于它们在 CHOICE 那一层做了 Tag Number 的区分,所以这里相同的 Tag Number 是允许的 Number ::= [0] INTEGER
二进制表示为1000 0000
, Tag Class 为CONTEXT-SPECIFIC
,TagNumber 为0
,INTEGER
是Primitive
类型
- 如果没有明确的写明 Tag Class,那么默认为 Context-Specific。如
-
private 在一家公司或一个国家内唯一标志一个类型,如
Number ::= [PRIVATE 0] INTEGER
二进制表示为1100 0000
, Tag Class 为PRIVATE
,TagNumber 为0
,INTEGER
是Primitive
类型
IMPLICIT & EXPLICIT 模式
IMPLICIT
当一个 Tag 被声明为 IMPLICIT 时,编码的时候会用 IMPLICIT 前面的 Tag 替换 IMPLICIT 后面的 Tag。
-
Number ::= [0] IMPLICIT INTEGER
- 用于替换的 Tag 二进制表示为
1000 0000
, Tag Class 为CONTEXT-SPECIFIC
,TagNumber 为0
,INTEGER
是Primitive
类型 - 被替换的 Tag 二进制表示为
0000 0010
, Tag Class 为UNIVERSAL
,TagNumber 为2
,INTEGER
是Primitive
类型
- 用于替换的 Tag 二进制表示为
-
Number ::= [0] IMPLICIT [1] INTEGER
- 用于替换的 Tag 二进制表示为
1000 0000
, Tag Class 为CONTEXT-SPECIFIC
,TagNumber 为0
,INTEGER
是Primitive
类型 - 被替换的 Tag 二进制表示为
1000 0001
, Tag Class 为CONTEXT-SPECIFIC
,TagNumber 为1
,INTEGER
是Primitive
类型
- 用于替换的 Tag 二进制表示为
-
当复合类型 SEQUENCE,SET 被声明为 IMPLICIT 模式的时候,它的成员变量也默认为 IMPLICIT,除非明确的指明某个成员为 EXPLICIT 模式。如下面的例子中
ID
和Name
都是 IMPLICIT 模式,Account
是 EXPLICIT 模式。1 2 3 4 5
User ::= [0] IMPLICIT SEQUENCE { ID [0] OCTET STRING Name [1] OCTET STRING Account [2] EXPLICIT OCTET STRING }
EXPLICIT
当一个 Tag 被声明为 EXPLICIT 时,编码的时候会使用 EXPLICIT 前面的 Tag 包裹 EXPLICIT 后面的 TLV 数据。
Number ::= [0] EXPLICIT INTEGER
- 这里假设 Number 的 Value 为
0x12345678
- EXPLICIT 后面的 TLV 编码为
020412345678
,02
是 Tag,04
是 Value 的长度,12345678
是 Value 值 - Number 的完整编码为
8006020412345678
,80
是新的 Tag,06
是 Value 的长度,020412345678
是包裹的 TLV
- 这里假设 Number 的 Value 为
- 在默认情况下,没有指明是 EXPLICIT 还是 IMPLICIT 的时候,使用的是 EXPLICIT 模式。上面的例子可以简写为
Number ::= [0] INTEGER
对于 Tag Class 为 UNIVERSAL 的数据来说,没有 IMPLICT 和 EXPLICT 的区分,下面四种声明是一致的:
Number ::= EXPLICIT INTEGER
Number ::= IMPLICIT INTEGER
Number ::= [UNIVERSAL 2] IMPLICIT INTEGER
Number ::= INTEGER