天天看點

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) {

    } 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的政策一緻,可以完全解除這個可悲的局面。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1271998

繼續閱讀