openssl中的一个bug--附带asn1的点点滴滴

openssl的源代码中有一个我自认为的bug,可能作者有意这么做,然而我却认为它是bug,起码我在验证证书序列号的号的时候,它出了问题。问题是这样的:用以下命令
openssl x509 -in somecert -serial -noout
可以输出一个证书的序列号,然而当该证书的序列号的第一个字节与数字0x80按位与之后的结果非0的话,那么就会将该数作为负数来对待,如果不信的话,那么你挑选几个序列号的第一个字节和0x80按位与非0的证书来用x509验证试一下,肯定会出现一个负数的序列号,这个错误相对不好重现,因为0x80只有一个位是1。
首先说一下openssl中的d2i和i2d操作,所谓的d就是der,所谓的i就是internal,d2i就是将证书的序列化格式化为内部的c语言数据结构格式,反过来亦然,这个看来令人不知所云的东西看起来是如此的简单,这正归功于asn结构的简洁(虽然它有很长的规范文档,我可没工夫看)。asn的结构基本就是(类型,长度,值)的三元组(我记得曾经在长春的一家公司搞过snmp,然后因为不愿加班离职了),而且是一个递归的嵌套结构,所谓的i2d其实并不需要什么内部运算,只需要将数据按照事先规定好的类型,长度,规则好就是了,d所表达的归根结底是一个字符串,这个字符串通过i结构体的类型长度进行规划,同时根据asn结构的内部含义进行分割,最终的d的形式就是一个char数组,这里面最根本的就是asn结构,另外具有语义意义的是i的结构体。首先看一下openssl中的普遍的结构ASN1_STRING,别的不管,asn-interger就是这个结构定义的:
typedef struct asn1_string_st {
int length;
int type;
unsigned char *data;
long flags;
} ASN1_STRING;
但是再看看c2i_ASN1_INTEGER这个函数:
ASN1_INTEGER *c2i_ASN1_INTEGER(ASN1_INTEGER **a, const unsigned char **pp, long len)
{
ASN1_INTEGER *ret=NULL;
const unsigned char *p, *pend;
unsigned char *to,*s;
int i;
if ((a == NULL) || ((*a) == NULL)) {
if ((ret=M_ASN1_INTEGER_new()) == NULL) return(NULL);
ret->type=V_ASN1_INTEGER;
} else
ret=(*a);
p= *pp;
pend = p + len;
s=(unsigned char *)OPENSSL_malloc((int)len+1);
to=s;
if(!len) {
ret->type=V_ASN1_INTEGER;
} else if (*p & 0x80) {
ret->type=V_ASN1_NEG_INTEGER;
...
} else {
...
}
...
err:
...
return(NULL);
}
由于asn完全不依赖外部结构,因此哪怕一个数据结构的类型都是由asn-interger定义的,如果一个证书的serial是由一个asn-interger表示的话,那么这个asn-interger的type也是一个asn-interger(一切都是递归的),于是看看上面的代码,else if (*p & 0x80)这一句,如果一个*p和0x80按位与不为0的话,那么该数就会被认为是负数,这是不应该的啊,事实是,只有一个asn-interger的type字段和0x80按位与不为0才能被认为是负数,于是这段代码是一段很可悲的代码,我的改进如下:
1.添加一个first_malloc变量,只有在一个变量被malloc的时候才置1,而只有在first_malloc置1的时候才验证*p和0x80按位与的情况;2.将ret->type=V_ASN1_INTEGER这个设置放到函数的最后,然后只有在ret->type为0的情况下才验证*p其与0x80按位与的值,这个策略也正和acl的策略一致,可以完全解除这个可悲的局面。

你可能感兴趣的:(OpenSSL)