【引言】
ASN.1全称为Abstract Syntax NotationOne,是一种描述数字对象的方法和标准。openssl的编码方法就是基于该标准的,目前,很多其他软件的编码方法也是基于该标准。对于直接使用openssl的API或者应用程序来说,可能对ASN.1的了解并不需要很清楚,但是为了使大家对后续介绍的各个API有一个更深刻的编码知识基础,所以对该ASN.1以及openssl相应提供的API处理函数作介绍。
【ASN.1概述】
ASN.1作为一个数字对象描述标准,包括了两部分,分别为数据描述语言(ISO8824)和数据编码规则(ISO8825)。ASN.1的数据描述语言标准允许用户自定义基本数据类型,并可以通过简单的数据类型组成更复杂的数据类型。比如一个复杂的数据对象,如X509证书,就是在其它一些数据类型上定义的,而其它数据类型又是在更基本的数据类型上建立的,直到回溯到定义的最基本的数据类型。
比如ASN.1定义的X509证书的一个子域Validity(证书有效期)就定义如下:
Validity ::= SEQUENCE
{
notBefore UTCTIME,
notAfter UTCTIME
}
其意义就是定义Validity为一个有序序列,由两个个UTCTIME类型的数据notBefore和notAfter组成。然后,就需要找出UTCTIME是怎么定义的,当然,事实上UTCTIME是ASN.1定义的一个基本的时间数据类型。
在上述数据定义的基础上,ASN.1定义了一组编码规则,以规定怎么将上述描述的对象转换成应用程序能够处理和进行传输的二进制编码形式。ASN.1定义了多种编码方法,包括了BER, DER, PER, 和XER等,不过,虽然最基本最常用的编码方式是BER(Basic EncodingRules),但是由于该编码方法可能对一个相同的对象有几种不同的合法二进制编码,所以在openssl里面使用的是BER的子集DER(Distinguished Encoding Rules),使用DER编码方法,对于每一个ASN.1对象,其相应的二进制编码是唯一的。
ASN.1里定义的每个基本对象都有一个对应的数字标识tag,在进行二进制编码的时候需要使用该标志。
【ASN.1定义的基本数据类型】
下面列出ASN.1定义的部分基本数据类型,其各字段的意义如下:
[数据类型]-[数据说明]-[Tag(16进制)]
[BOOLEAN]-[有两个值:false或true]-[01]
[INTEGER]-[整型值]-[02]
[BIT STRING]-[0位或多位]-[03]
[OCTET STRING]-[0字节或多字节]-[04]
[NULL]-[NULL值]-[05]
[OBJECT IDENTIFIER]-[相应于一个对象的独特标识数字]-[06]
[OBJECT DESCRIPTOR]-[一个对象的简称]-[07]
[EXTERNAL]-[ASN.1没有定义的数据类型]-[08]
[REAL]-[实数值]-[09]
[ENUMERATED]-[数值列表,这些数据每个都有独特的标识符,作为ASN.1定义数据类型的一部分]-[0a]
[SEQUENCE和SEQUENCE OF]-[有序数列,SEQUENCE里面的每个数值都可以是不同类型的,而SEQUENCE OF里是0个或多个类型相同的数据]-[10]
[SET和SET OF]-[无序数列,SET里面的每个数值都可以是不同类型的,而SET OF里是0个或多个类型相同的数据]-[11]
[NumericString]-[0-9以及空格]-[12]
[PrintableString]-[A-Z、a-z、0-9、空格以及符号 ()+,-./:=?]-[13]
[UTCTime]-[统一全球时间格式]-[17]
除了上述基本类型,ASN.1还定义了另外一些专用的数据类型,这里不再一一叙述。
openssl之ASN.1系列之2---ASN.1编码方法简介
参考资料:“Computer Network”,“A Layman s Guide to a Subset of ASN.1, BER, and DER”
ASN.1对象的编码是ASN.1标准的重要部分,目前,通常采用的是BER,而DER则是其一个子集。本文将对该编码方法作简单的介绍。
一个标准的ASN.1编码对象有四个域:对象标识域、数据长度域、数据域以及结束标志(可选,在长度不可知情况下需要,openssl中没有该标志)。
【对象标识域】
对象标识域有两种形式,低Tag数字(Tag值在0到30)和高Tag数字(Tag值大于30)形式。
低Tag数字形式只有一个字节,包含三部分,从低位为1开始编号,8和7位是Tag类型,共有四种,分别是universal(00)、application(0 1)、context-specific(1 0)和private(11);第6位是0,表明编码类型是基本类型,第5-1位是Tag值。
高Tag数字形式可以有两个或多个字节,第一个字节跟低Tag数字形式一样,但低5位值全为1,而在后续的第二个和其后的字节中给出Tag值,这些字节都只使用了低7位为数据位,最高位都设为0,但最后一个字节的最高位设为1,采用高位优先,经可能少的数字原则。
【数据长度域】
数据长度域也有两种形式,短形式和长形式。
短形式的数据长度域只有一个字节,第8位为0,其它低7位给出数据长度。
长形式的数据长度域有2到127个字节。第一个字节的第8位为1,其它低7位给出后面该域使用的字节的数量,从该域第二个字节开始给出数据的长度,基于256,高位优先。
【数据域】
数据域给出了具体的数据值。该域的编码对不同的数据类型不一样,这里就不在一一详述了,有兴趣的可以参看参考资料。
【一个编码例子】
下面是SSLDocument给出的对一个对象进行DER编码的例子,更多的例子可以参看本文给出的参考资料。
例子使用的对象是ASN.1定义的BIT STRING类型的对象,其编码的步骤如下:
1.对位串使用"0"进行填补,使其长度为8的整数倍(如果已经是整数倍,则不需要进行填补);
2.计算填补的位数并写下来,成为数据内容的第一个字节;
3.写入填补后的位串,高位字节优先。这些数据跟前面的一个字节组成数据内容的全部字节;
4.在这些数据前面加上一个头字节,这个字节定义如下(编号是从低位为1开始):
第8、7位:00(universal类型)
第 6 位 :0(表明是基本类型,有限长度的编码)
第5-1位:0x03(表明是BIT STRING)
这个字节定义了对象标识域;
5.然后在对象标识域字节和数据字节之间加入下面计算的定义的字节:
计算有多少字节的数据内容(对象标识域数据除外),如果少于127字节,那么就定义一个字节如下:
第8位:0
第7-1位:数据内容的字节数量
如果数据内容的字节数量大于127,就需要定义两个或多个字节,其中,第一个字节的定义如下:
第8位:1
第7-1位:该域后面还有多少字节
其后的字节是数据内容的字节数量,每字节基于256,高位优先
下面是一个实际的数据例子:
位串: 01000100111011
1.补齐两个0在后面,成为8的整数倍,得到 0100010011101100 ;
2. 02 作为第一个数据内容的字节;
3. 44 ec 作为其余的数据内容的字节;
4. 03 作为前面的对象标识字节;
5.因为BIT STRING的tag值3<=127,所以只有一个字节的长度域 03 ;
那么得到的这个位串的DER编码就是03 03 02 44 ec,其中,第一个字节是对象标识域,第二个字节是数据长度域,其他为数据域。
openssl之ASN.1系列之3---ASN.1函数概述和结构
【ASN.1函数库概述】
因为X509相关的协议都是基于ASN.1和DER编码的,所以openssl提供了一组函数,这些函数可以读取DER编码的对象,并将它们转换成openssl能够处理的内部格式;这些函数也可以将openssl里定义的C格式的对象结构转换成DER编码的对象。此外,该系列还提供了一些对这些对象进行比较、读取和设定指定值的函数。该系列函数还包括了一些签名函数,这是因为在签名之前,有些对象需要进行DER编码。
下面对ASN.1函数库中重要的数据结构做简单的介绍。
【ASN1_CTX】
该结构用来在ASN1处理过程中维护跟踪各种相关变量,其定义如下:
typedef struct asn1_ctx_st
{
int eos;
int error;
int inf;
int tag;
int xclass;
long slen;
unsigned char *max;
unsigned char *q;
unsigned char **pp;
int line;
- 参数p是工作字符指针,其最大长度由参数max指定;
- eos是indefinite编码模式的结束标识标志;
- error是错误代码;
- inf值为0x20代表constructd模式,为0x21代表indefinite模式;
- tag是最后取得的对象的tag值;
- xclass是最后取得的对象的类型;
- slen是最后取得的对象的长度;
- line变量在出错处理的时候使用。
该结构用来保存一个ASN1对象,其定义如下:
typedef struct asn1_object_st
{
char *sn,*ln;
int nid;
int length;
unsigned char *data;
int flags;
}ASN1_OBJECT;
- nid是openssl内部定义的每个数字对象的独特标识码;
- sn是对象的简称;
- ln是对象的长名或小写名;
- data是相应对象的数据,
- length是该data字段的长度,
- flags是一个释放标志。
该结构是openssl里一个很基本的ASN.1对象结构,Openssl里定义的很多类型的对象都是采用该结构的,他们包括ASN1_INTEGER、ASN1_BIT_STRING、ASN1_OCTET_STRING、ASN1_PRINTABLESTRING、ASN1_T61STRING、ASN1_IA5STRING、ASN1_UTCTIME、ASN1_GENERALIZEDTIME、ASN1_GENERALSTRING、ASN1_UNIVERSALSTRING和ASN1_BMPSTRING。该结构的定义如下:
typedef struct asn1_string_st
{
int length;
int type;
unsigned char *data;
- type参数指明对象的类型;
- data参数是对象的数据,
- length指定了其长度;
- flags值跟type有关,一般来说,在BIT_STRING对象中使用。
该结构可以保存任意类型的ASN.1对象,其定义如下:
typedef struct asn1_type_st
{
int type;
union
{
char *ptr;
ASN1_BOOLEAN boolean;
ASN1_STRING * asn1_string;
ASN1_OBJECT * object;
ASN1_INTEGER * integer;
ASN1_ENUMERATED * enumerated;
ASN1_BIT_STRING * bit_string;
ASN1_OCTET_STRING * octet_string;
ASN1_PRINTABLESTRING * printablestring;
ASN1_T61STRING * t61string;
ASN1_IA5STRING * ia5string;
ASN1_GENERALSTRING * generalstring;
ASN1_BMPSTRING * bmpstring;
ASN1_UNIVERSALSTRING * universalstring;
ASN1_UTCTIME * utctime;
ASN1_GENERALIZEDTIME * generalizedtime;
ASN1_VISIBLESTRING * visiblestring;
ASN1_UTF8STRING * utf8string;
ASN1_STRING * set;
ASN1_STRING * sequence;
} value;
} ASN1_TYPE;
其中,参数type指定了对象的类型。
【ASN1_METHOD】
该结构包含了指向一组函数的指针,这些函数定义了在openssl内部结构和DER编码对象之间进行格式转换的功能,还定义了分配和释放一个结构的功能。其定义如下:
typedef struct asn1_method_st
{
int (*i2d)();
char *(*d2i)();
char *(*create)();
void (*destroy)();
} ASN1_METHOD;
- i2d指向的函数将内部格式转换成DER编码格式;
- d2i指向的函数将DER编码的对象转换成内部结构;
- create指向的函数给新对象分配内存;
- destroy指向的函数释放对象的内存。
static ASN1_METHOD meth=
{
(int (*)()) i2d_X509,
(char *(*)())d2i_X509,
(char *(*)())X509_new,
(void (*)()) X509_free
};
ASN1_METHOD *X509_asn1_meth()
{
return(&meth);
}
【ASN1_HEADER】
该结构只在Netscape格式的证书里使用了(参考apps/x509.c文件),其定义如下:
typedef struct asn1_header_st
{
ASN1_OCTET_STRING *header;
char *data;
ASN1_METHOD *meth;
} ASN1_HEADER;
除了上述介绍的基本结构外,ASN.1相关的结构还有几个,但这些因为不是特别通用,在这里不再作介绍,有兴趣可以看文件x509.h。
openssl之ASN.1系列之4---ASN.1对象的构造和释放
基本的对象构造和释放函数定义如下(crypto\asn1\asn1.h):
ASN1_OBJECT * ASN1_OBJECT_new(void );
void ASN1_OBJECT_free(ASN1_OBJECT *a);
ASN1_OBJECT * ASN1_OBJECT_create(int nid, unsigned char *data,int len,const char *sn, const char *ln);
ASN1_STRING * ASN1_STRING_new(void);
void ASN1_STRING_free(ASN1_STRING *a);
ASN1_STRING * ASN1_STRING_type_new(int type );
ASN1_HEADER * ASN1_HEADER_new(void );
void ASN1_HEADER_free(ASN1_HEADER *a);
ASN1_VALUE * ASN1_item_new(const ASN1_ITEM *it);
void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it);
【ASN1_OBJECT】
该系列的new和free函数分别完成了ASN1_OBJECT对象的创建和释放。在创建ASN1_OBJECT对象的时候,该函数给对象分配内存空间,并将结构内所有指针类型的变量值都设为NULL,nid和长度都初始化为0,并将flags设置为ASN1_OBJECT_FLAG_DYNAMIC,返回创建的对象的指针,如果失败,返回NULL。对象释放的时候,free函数将所有对象成员的内存和自身的内存释放,并将lenght设置为0。
除了使用ASN1_OBJECT_new创建ASN1_OBJECT对象,还可以使用ASN1_OBJECT_create函数创建对象,该函数根据给定的参数创建一个ASN1_OBJECT对象。其中,nid是对象的独特标识NID;data为对象的数据,len指定了data有效数据的长度;sn是对象的简称;ln是对象的完整名字或小写名字;该函数将falgs标志设为ASN1_OBJECT_FLAG_DYNAMIC|ASN1_OBJECT_FLAG_DYNAMIC_STRINGS|ASN1_OBJECT_FLAG_DYNAMIC_DATA。该函数返回一个对象结构体,而不是指针。
【ASN1_STRING】
ASN1_STRING_type_new函数根据给定的参数type创建并返回一个ASN1_STRING对象指针。事实上,该函数不管type是什么,都是创建一个ASN1_STRING对象,然后将成员data初始化为NULL,flags和length初始化为0。唯一有区别的就是令成员变量type的值为参数type的值,目前支持的type值如下:
V_ASN1_BIT_STRING
V_ASN1_INTEGER
V_ASN1_ENUMERATED
V_ASN1_OCTET_STRING
V_ASN1_T61STRING
V_ASN1_PRINTABLESTRING
V_ASN1_VISIBLESTRING
V_ASN1_IA5STRING
V_ASN1_UTCTIME
V_ASN1_GENERALIZEDTIME
V_ASN1_GENERALSTRING
V_ASN1_UNIVERSALSTRING
V_ASN1_BMPSTRING
V_ASN1_UTF8STRING
ASN1_STRING_new函数是调用返回了ASN1_STRING_type_new函数实现的,其type参数为V_ASN1_OCTET_STRING。上述两个函数成功执行返回一个ASN1_STRING的指针,否则返回NULL。ASN1_STRING_free函数释放了用上述两个函数创建ASN1_STRING对象,没有返回值。
【ASN1_HEADER】
这两个函数用于处理Netscape格式的证书和私钥对象。事实上,new函数创建了一个ASN1_HEADER对象,并调用了ASN1_STRING_type_new函数,使用type参数为V_ASN1_OCTET_STRING对该对象的header成员变量进行初始化,并将meth和data设置为NULL,返回ASN1_HEADER对象的指针。free函数释放该类型对象,没有返回值。
【ASN1_VALUE】
这两个函数一般已经不在使用,只是为了兼容以前的版本而保留了下来。这里不再作介绍。
【基于上述基本函数的宏定义函数】
基于上述的基本函数,尤其是ASN1_STRING_type_new函数,openssl提供了很多为了方便用户使用的宏定义,这些宏定义的形式是如下述形式:
构造函数:M_对象名_new()
释放函数:M_对象名_free(a)
由于函数较多,具体可以参考asn1.h文件,这里不再一一列出了。