ASN.1 语法・二

ASN.1 抽象语法描述规则

Posted by mingfer on April 5, 2019

本篇是前一篇 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 结构组成。该结构中包含 idnamephoneNumber 三个字符串信息和 address 这个Address 信息。Address 又由 provincecitystreet 三个字符串信息组成的 SEQUENCE 结构定义。

抽象语法中的 Tag

上一遍 X.690 规范 有说道 ASN.1 的 tag 具有很多的类型,那么这些类型是如何在抽象语法中体现的呢?在证书结构的定义中我们可以看到 issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, 这样的数据结构,这里的 [1]IMPLICITOPTIONAL 代表什么呢?

Tag 一共有四种类型:universalapplicationcontext-specificprivate,这里不推荐使用 applicationprivate

  • 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 ,假设 NumericStringPrimitive 类型。

  • context-specific 的 Tag 只能出现在 SEQUENCESETCHOICE 类型的组件中,如果这些结构类型本身也是另一个结构类型的组成,也可以递归的使用。只要不产生歧义,相同的 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 中都有 ab 两个元素,由于它们在 CHOICE 那一层做了 Tag Number 的区分,所以这里相同的 Tag Number 是允许的
    • Number ::= [0] INTEGER 二进制表示为 1000 0000, Tag Class 为 CONTEXT-SPECIFIC,TagNumber 为 0INTEGERPrimitive 类型
  • private 在一家公司或一个国家内唯一标志一个类型,如 Number ::= [PRIVATE 0] INTEGER 二进制表示为 1100 0000, Tag Class 为 PRIVATE,TagNumber 为 0INTEGERPrimitive 类型

IMPLICIT & EXPLICIT 模式

IMPLICIT

当一个 Tag 被声明为 IMPLICIT 时,编码的时候会用 IMPLICIT 前面的 Tag 替换 IMPLICIT 后面的 Tag。

  • Number ::= [0] IMPLICIT INTEGER

    • 用于替换的 Tag 二进制表示为 1000 0000, Tag Class 为 CONTEXT-SPECIFIC,TagNumber 为 0INTEGERPrimitive 类型
    • 被替换的 Tag 二进制表示为 0000 0010, Tag Class 为 UNIVERSAL,TagNumber 为 2INTEGERPrimitive 类型
  • Number ::= [0] IMPLICIT [1] INTEGER

    • 用于替换的 Tag 二进制表示为 1000 0000, Tag Class 为 CONTEXT-SPECIFIC,TagNumber 为 0INTEGERPrimitive 类型
    • 被替换的 Tag 二进制表示为 1000 0001, Tag Class 为 CONTEXT-SPECIFIC,TagNumber 为 1INTEGERPrimitive 类型
  • 当复合类型 SEQUENCE,SET 被声明为 IMPLICIT 模式的时候,它的成员变量也默认为 IMPLICIT,除非明确的指明某个成员为 EXPLICIT 模式。如下面的例子中 IDName 都是 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 编码为 02041234567802 是 Tag,04 是 Value 的长度,12345678 是 Value 值
    • Number 的完整编码为 800602041234567880 是新的 Tag,06 是 Value 的长度,020412345678 是包裹的 TLV
  • 在默认情况下,没有指明是 EXPLICIT 还是 IMPLICIT 的时候,使用的是 EXPLICIT 模式。上面的例子可以简写为 Number ::= [0] INTEGER

对于 Tag Class 为 UNIVERSAL 的数据来说,没有 IMPLICT 和 EXPLICT 的区分,下面四种声明是一致的:

  • Number ::= EXPLICIT INTEGER
  • Number ::= IMPLICIT INTEGER
  • Number ::= [UNIVERSAL 2] IMPLICIT INTEGER
  • Number ::= INTEGER