ASN.1 语法及 X.509 证书格式解析解析
实验室有个项目需要用 Zeek 解析证书公钥信息,不过 zeek 并不支持该功能。而目前网上都是直接调用 openssl 来提取公钥。无奈之下,只能自己实现一个了。
语法
ASN.1 语法格式
ASN.1(Abstract Syntax Notation dot one)是一种抽象语法标记。用来定义抽象数据类型形式的标准,可以用来描述一个协议、数据结构应该长什么模样。尽管设计目的上不涉及数据结构如何存储,但由于使用广泛,实际上存在一套默认的规范。
作为一个抽象语法,其主要目的是描述数据结构(类型定义),可以与 TypeScript 做类比(某种程度上,这里的语法和 TS 很相似)。需要考虑的语法只有一个 类型名 ::= 基础类型 { 类型具体描述 },根据不同的基础类型进行拼接组合即可(基础类型见下文)
如果我们需要设计一个 Blog 协议,包含地址、标题、日期、正文、状态。那么可以按照如下格式进行定义
Blog ::= SEQUENCE {
address PrintableString,
title UTF8String,
date UTCTime,
content String,
status BlogStatus
}
BlogStatus ::= ENUMERATED {
Draft(0),
Published(1),
Hidden(2)
}
X.509 证书定义样例
Certificate ::= SIGNED SEQUENCE{
version [0] Version DEFAULT v1988,
serialNumber CertificateSerialNumber,
signature AlgorithmIdentifier,
issuer Name,
validity Validity,
subject Name,
subjectPublicKeyInfo SubjectPublicKeyInfo
}
Version ::= INTEGER {v1988(0)}
CertificateSerialNumber ::= INTEGER
Validity ::= SEQUENCE{
notBefore UTCTime,
notAfter UTCTime
}
SubjectPublicKeyInfo ::= SEQUENCE{
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}
AlgorithmIdentifier ::= SEQUENCE{
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
编码
ASN.1 严格包含三部分内容:类型编码、长度编码、数据
类型编码
ASN.1 中类型共占一个字节,由三部分组成:类别位、结构化位、原始类型
类别位
表示类型要解释的上下文,占据高 位
| 第 8 位 | 第 7 位 | 含义 |
|---|---|---|
| 0 | 0 | 通用(Universal) |
| 0 | 1 | 应用(Application) |
| 1 | 0 | 上下文特定(Context Specific) |
| 1 | 1 | 专用(Private) |
结构化位
对于容器类型(如列表),用来表示容器内各个元素类型是否相同。如果相同,则可以只声明一次类型。占据第 位
原始类型
表示数据的基本类型,占据低 位
| 序号 | 十六进制 | 类型 | 解释 |
|---|---|---|---|
| 1 | 0x01 |
BOOLEAN | 布尔类型 |
| 2 | 0x02 |
INTEGER | 整数 |
| 3 | 0x03 |
BIT_STRING | 比特流 |
| 4 | 0x04 |
OCTET_STRING | 字节流 |
| 5 | 0x05 |
NULL | 空值 |
| 6 | 0x06 |
OID | 对象标识符 |
| 16 | 0x10 |
SEQUENCE | 数组 |
| 17 | 0x11 |
SNMP_SET | 集合 |
| 19 | 0x13 |
PrintableString | ASCII编码可视字符 |
| 20 | 0x14 |
T61_STRING | |
| 22 | 0x16 |
IA5_String | ASCII编码 |
| 23 | 0x17 |
UTCTIME | 时间戳 |
长度编码
长度编码由一个字节作为起始,包含如下三种类型
短编码
最高位为 ,低 位表示 的长度。
如 0x05 表示数据长度为 5
长编码
最高位为 ,低 位表示 的长度,而后存在该长度的字节存储实际的长度。
如 0x82 0x04 0x92 中,0x82 表示使用长编码,且长度位为 ,后续 0x04 0x92 为实际的数据长度,为
Zeek 解析证书公钥
根据上面的语法,可以看出来实际上 ASN.1 只需要一个简单的递归函数即可搞定。
由于这里只需要提取一个字段,甚至不必全文解析。
首先要实现一个 ASN.1 类型解析的函数(Zeek Script 语法,别的语言肯定比这写起来更容易)
function read_part(content:string): ASN1Object {
local result = ReadBytesResult();
result= read_n_bytes(content, 1);
content = result$content;
local object_type = bytestring_to_count(result$result);
if (object_type == 0) {
return ASN1Object(
$data_type=object_type,
$length=0,
$value="",
$rest=content
);
}
result = read_n_bytes(content, 1);
content = result$content;
local object_length = bytestring_to_count(result$result);
if (object_length & 0x80 > 0) {
object_length = object_length & 0x7f;
result = read_n_bytes(content, object_length);
content = result$content;
object_length = bytestring_to_count(result$result);
}
result = read_n_bytes(content, object_length);
local object_value = result$result;
content = result$content;
return ASN1Object(
$data_type=object_type,
$length=object_length,
$value=object_value,
$rest=content
);
}
接下来按照 X.509 规定的格式,反复调用该函数即可


中文博客导航
萌ICP备20213456号