新闻  |   论坛  |   博客  |   在线研讨会
X.509证书的编码及解析:程序解析以及winhex模板解析
电子禅石 | 2021-08-09 17:19:45    阅读:3221   发布文章

一、证书的整体结构:证书内容、签名算法、签名结果。

用ASN.1语法描述如下:

Certificate::=SEQUENCE{
    tbsCertificate      TBSCertificate,
    signatureAlgorithm  AlgorithmIdentifier,
    signatureValue      BIT STRING
}

其中,签名算法为CA对tbsCertificate进行签名所使用的算法;类型为AlgorithmIdentifier,其ASN.1语法描述如下:

AlgorithmIdentifier::=SEQUENCE{
    algorithm       OBJECT IDENTIFIER,
    parameters      ANY DEFINED BY algorithm OPTIONAL
}

其中,algorithm给出了算法的OID;可选的parameters给出算法的参数。

需要注意,algorithm同时说明了杂凑算法和数字签名算法,常见的有:(1)MD5wihRSAEncryption, MD5 Hash函数和RSA签名算法配合使用,OID为1.2.840.113549.1.1.4。(2)SHA1withRSAEncryption, SHA-1 Hash函数和RSA签名算法配合使用,OID为1.2.840.113549.1.1.5。

签名结果是CA对tbsCertificate进行签名的结果,类型为BIT STRING。

证书内容是需要被CA签名的信息,ASN.1语法描述如下:

复制代码
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,
    subjectUniqueID   [2]   IMPLICIT UniqueIdentifier OPTIONAL,
    extensions        [3]   EXPLICIT Extensions OPTIONAL
}
复制代码

其中,issuerUniqueID和subjectUniqueID只能在版本2或者3中出现;extensions只能在版本3中出现。

 

下面我们逐一说明TBSCertificate中的每一个字段。

1>版本号

版本(version)为整数格式。到目前为止,证书格式的版本只有v1、v2、v3,分别用整数0、1、2表示。

其类型Version的ASN.1描述如下:

Version::=INTEGER {v1(0),v2(1),v3(2)}

目前最常用的版本是v3。

2>序列号

证书序列号(serialNumber)为整数格式。

其类型CertificateSerialNumber的ASN.1描述如下:

CertificateSerialNumber::=INTEGER

证书序列号用来在某一个CA范围内唯一地标识一张证书。由此,“签发者”和“证书序列号”配合起来就能唯一地标识一张数字证书。在很多PKI的通信协议中使用的就是这种方式。

RFC 3280标准要求证书序列号必须是正整数,且长度不应该大于20字节。

3>签名算法

签名算法(signature)给出了CA签发证书时所使用的数字签名算法,它的类型与signatureAlgorithm的类型相同,都为AlgorithmIdentifier,它们的值必须一致,否则该证书无效。

4>签发者和主体

证书的签发者(issuer)和证书主体(subject)分别标识了签发证书的CA实体和证书持有者实体,两者类型均为Name。ASN.1描述如下:

复制代码
Name::=CHOICE{
    RDNSequence
}
RDNSequence::=SEQUENCE OF RelativeDistinguishedName
RelativeDistinguishedName::=SET OF AttributeTypeAndValue
AttributeTypeAndValue::=SEQUENCE{
    type    AttributeType,
    value   AttributeValue
}
AttributeType::=OBJECT IDENTIFIER
AttributeValue::=ANY DEFINED BY AttributeType
复制代码

证书的签发者和证书主体用X.509 DN表示,DN是由RDN构成的序列。RDN用“属性类型=属性值”的形式表示。常用的属性类型名称以及简写如下:

常用RDN属性类型
属性类型名称含义简写
Common Name通用名称CN
Organizational Unit name机构单元名称OU
Organization name机构名O
Locality地理位置L
State or province name州/省名S
Country国名C

5>有效期

证书有效期(validity)给出证书的有效使用期,包含起、止两个时间值。时间值可以使用UTCTime或者GeneralizedTime的形式表示。ASN.1描述如下:

复制代码
Validity::=SEQUENCE{
    notBefore       Time,
    notAfter        Time
}
Time::=CHOICE{
    utcTime         UTCTime,
    generalTime     GeneralizedTime
}
复制代码

6>主体公钥信息

主体公钥信息(subjectPublicKeyInfo)给出了证书所绑定的加密算法和公钥。其ASN.1描述如下:

SubjectPublicKeyInfo::=SEQUENCE{
    algorithm           AlgorithmIdentifier,
    subjectPublicKey    BIT STRING
}

其中,algorithm表示被绑定的、证书主体持有的公钥密码算法;subjectPublicKey是具体的公钥数据,内容和格式依算法不同而异。对于RSA算法,它包含公钥参数e和n。

7>签发者唯一标识符和主体唯一标识符

签发者唯一标识符(issuerUniqueID)和主体唯一标识符(subjectUniqueID)给出了证书签发者和证书主体的唯一标识符。UniqueIdentifier类型的ASN.1描述如下:

UniqueIdentifier::=BIT STRING

 

二、证书编码

针对ASN.1的语法,编码可以采用“TLV”方式,即依次对数据的类型(type)、长度(length)、值(value)编码,这样就可以完整地表示一个特定类型的数据。“TLV”方式的编码有多种,下面介绍DER这种编码方式。都是big-endian字节序。

1.简单类型的编码

1>BOOLEAN:01

布尔类型,两种取值:TRUE(0xFF)、FALSE(0x00)。

编码为:

        T      L      V
TRUE    01     01     FF
FALSE   01     01     00

2>INTEGER:02

整数类型。两种情况:

第一种,数据长度不大于0x7F,称为“短形式”,length占1字节,直接把长度赋给length。举例:0x123456的DER编码为:

T   L   V02  03  12  34 56

第二种,数据长度大于0x7F,称为“长形式”,把数据长度L表示为字节码,计算其长度n,然后把n与0x80进行“位或”运算的结果赋给length的第一个字节。举例:0x1234...34(长0100字节),即n=2,编码为:

T   L           V02  82  01  00  12  34 ...  34

此外,对于整数,还有正负的问题。规定value的最高位表示符号---0(+) 1(-)  负数用补码表示。

1)对于正数,如最高位为1,则向左扩展00。

2)对于负数,如其补码的最高位为0,则向左扩展FF。

3>BIT STRING:03

比特串的长度可能不是8的倍数,而DER编码以字节为单位。故而,如果需要,则在比特串的最后填若干位“0”,使其长度达到8的倍数;在最前面增加1字节,写明填充的位数。特别注意:value部分的第一字节,即表示填充位数的那个字节,也要计入数据的总长度。如果不需要填充,则第一字节也需要用00来表示填充位数。举例:1011010010编码为:

T   L   V03  03  06  B4 80

4>OCTET STRING:04

字节码串。举例:AB CD EF 01 23的编码为:

T   L   V04  05  AB  CD  EF  01  23

5>NULL:05

编码是固定的,value部分为空,一共两字节:

T   L05  00

6>OBJECT IDENTIFIER:06

对象标识符(OID),是一个用“.”隔开的非负整数组成的序列。下面说下OID的编码设计:设OID=V1.V2.V3.V4.V5....Vn,则DER编码的value部分规则如下:(1)计算40*V1+V2作为第一字节;(2)将Vi(i>=3)表示为128进制,每一个128进制位作为一个字节,再将除最后一个字节外的所有字节的最高位置1;(3)依次排列,就得到了value部分。举例:OID=1.2.840.11359.1.1的编码如下:

说明:Vi的最后一个字节不对最高位置1,系统以此来识别这里是这个字段的最后一字节。

7>PrintableString:13

表示任意长度的ASCII字符串。举例:“Hello, world”的编码为:

T   L   V13  0C  48  65  6C  6C  6F  2C  20  77  6F  72  6C  64

8>UTCTime:17

表示时间,可以用GMT格林威治时间(结尾标“Z”)来表示,或者是用本地时间和相对于GMT的偏移量来表示。

UTCTime的格式如下多种:
YYMMDDhhmmZ
YYMMDDhhmm+hh'mm'
YYMMDDhhmm-hh'mm'
YYMMDDhhmmssZ
YYMMDDhhmmss+hh'mm'
YYMMDDhhmmss-hh'mm'

其中,

YY:年的最后2位
MM:月,01-12
DD:日,01-31
hh:小时,00-23
mm:分钟,00-59
ss:秒,00-59
Z/+/-:Z表示GMT时间,+/-表示本地时间与GMT时间的差距
hh’:与GMT的差
mm’:与GMT的差

举例:北京时间2008年8月8日晚8时表示成UTCTime为:080808120000Z 或 080808200000-0800 其编码为:

T   L   V17  0D  30  38  30  38  30  38  31  32  30  30  30  30  5A
或
T   L   V17  11  30  38  30  38  30  38  32  30  30  30  30  30  2D  30  38  30  30

9>GeneralizedTime:18

与UTCTime类似,差别只在于用4位数字表示“年”,以及“秒”可精确到千分位。举例:北京时间2008年8月8日晚8时1分2.345秒表示成GeneralizedTime为:20080808120102.345Z 或 20080808200102.345-0800 其编码为:

T   L   V18  13  32  30  30  38  30  38  30  38  31  32  30  31  30  32  2E  33  34  35  5A
或
T   L   V18  17  32  30  30  38  30  38  30  38  32  30  30  31  30  32  2E  33  34  35  2D  30  38  30  30

2.构造类型数据的编码

1>序列构造类型:30

SEQUENCE与SEQUENCE OF的type相同,都是30。value部分为序列内所有项目的编码的依次排列。length为这些项目编码的总长度。举例:一天中几次温度测量的结果:temperatureInADay SEQUENCE(7) OF INTEGER::={21,15,5,-2,5,10,5}, 其DER编码为:

复制代码
T   L   V30  15  02  01  15
        02  01  0F        02  01  05
        02  01  FE        02  01  05
        02  01  0A        02  01  05
复制代码

构造类型的定义中,常常包含CHOICE、ANY、OPTIONAL、DEFAULT等关键字,其编码规则如下:

(1)CHOICE

多选一,按照实际选中的类型编码。举例:

Time::=CHOICE{
    utcTime         UTCTime,
    generalizedTime GeneralizedTime
}

若实际用到的类型是UTCTime,则数据用UTCTime的编码规则编码。

(2)ANY

类型依赖于另一个域的值,则按照实际类型编码。举例:

AlgorithmIdentifier::=SEQUENCE{
    algorithm       OBJECT IDENTIFIER,
    parameters      ANY DEFINED BY algorithm OPTIONAL
}

若algorithm的值表示RSA,则parameters按RSA算法的参数类型编码;若algorithm的值表示Diffie-Hellman算法,则parameters按Diffie-Hellman算法的参数类型编码。

(3)OPTIONAL

所标记的字段在实际中可能存在,也可能不存在。如果有值,则编码;如果无值,则直接跳过。举例:

AlgorithmIdentifier::=SEQUENCE{
    algorithm       OBJECT IDENTIFIER,
    parameters      ANY DEFINED BY algorithm OPTIONAL
}

实际中,如果没有参数parameters,则相当于

AlgorithmIdentifier::=SEQUENCE{
    algorithm       OBJECT IDENTIFIER
}

(4)DEFAULT

如果所标记的字段在实际中正好等于缺省值,则可以编码也可以不编码,相当于是OPTIONAL;如果不等于缺省值,则应该如实编码。举例:

Certificate::=SEQUENCE{
    version           Version DEFAULT 0
    ......
}

若version的值恰好等于0(缺省值),则可以不编码;否则,必须按其类型编码。

2>集合构造类型:31

SET和SET OF的type都是31,value部分包括集合内所有项目的编码,length为其总长度。需要注意的是,集合构造类型中的各字段是并列的,逻辑上不分先后,但为了编码的唯一性,在DER编码中,编码的排列是有一定顺序的。SET按标签的顺序排列。举例:

Name::=SET{
    surname     [0] PrintableString,
    mid-name    [1] PrintableString,
    first-name  [2] PrintableString
}

编码时则按照surname,mid-name,first-name的顺序。

SET OF按字典升序排列,即将各项目的DER结果看做字节码从小到大排列。举例:一天中几次温度测量的结果:temperatureInADay SET(7) OF INTEGER::={21,15,5,-2,5,10,5}, 其DER编码为:

复制代码
T   L   V30  15  02  01  05
        02  01  05
        02  01  05
        02  01  0A        02  01  0F        02  01  15
        02  01  FE
复制代码

由于排序需要一定的时间和空间代价,故而实际情况中,应避免使用集合构造类型。

3.标签

仅仅以上的编码规则是不够的,会有些出现歧义的情况。比如:有相邻的字段属于相同的数据类型。type相同,则根据编码的排列顺序来区分他们。一旦其中有字段是可选的,解码时就不能再仅仅根据排列顺序来判断下一个是哪个字段了,产生歧义。故而,引入了标签,目的是把相同的type标签为不同的type,以便区分。

标签分为隐式标签和显式标签两种。分别如下:

隐式标签:

举例:

复制代码
Contact::=SEQUENCE{
    name                        PrintableString,
    sex                         BOOLEAN,
    title       [0] IMPLICIT    PrintableString OPTIONAL,
    locality    [1] IMPLICIT    PrintableString OPTIONAL,
    telephone   [2] IMPLICIT    PrintableString OPTIONAL,
    fax         [3] IMPLICIT    PrintableString OPTIONAL
}
复制代码

DER编码时,对于加了标签的项目,按如下规则编码:

对于简单类型,type=80+tag序号;对于构造类型,type=A0+tag序号。length和value不变。

例如,上例中如果项目fax被赋值为“86-10-12345678”,则编码为

T   L   V83  0E  38  36  2D  31  30  2D  31  32  33  34  35  36  37  38

显式标签:

举例:(隐式标签的例子)

复制代码
Record::=SEQUENCE{
    ......
    time    [1] IMPLICIT    Time    OPTIONAL,
    ......
}
Time::=CHOICE{
    utcTime             UTCTime,
    generalizedTime     GeneralizedTime
}
复制代码

假设time被赋值为UTCTime类型的值080808120000Z,而由于隐式标签的type编码覆盖了表示这一类型的type编码,导致编码时无法判断time究竟是哪种类型,造成混乱。于是这里需要使用显式标签。运用显式标签,上例描述为:

复制代码
Record::=SEQUENCE{
    ......
    time    [1] EXPLICIT    Time    OPTIONAL,
    ......
}
Time::=CHOICE{
    utcTime             UTCTime,
    generalizedTime     GeneralizedTime
}
复制代码

编码规则如下:

T           L                       V
A0+Tag序号  原TLV格式编码的总长度   原TLV格式编码

上例中time=080808120000Z的编码为:

T   L   V
A1  0F  17  0D  30  38  30  38  30  38  31  32  30  30  30  30  5A

事实上,显式标签就是在原编码外再封装一层。

三、证书解析 C程序

X.509证书的编码及解析:程序解析以及winhex模板解析 - jiu~ - 博客园 (cnblogs.com)

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
电子禅石  2021-08-09 17:23:33 

https://www.cnblogs.com/jiu0821/p/4598352.html

属于自己的技术积累分享,成为嵌入式系统研发高手。
推荐文章
最近访客