本文剖析asn-oid.h/c,從源代碼來學(xué)習(xí)eSNACC對OBJECT IDENTIFIER的編碼和解碼。
在研究代碼之前,我們先來說明什么是OBJECT IDENTIFIER。
————————————以下一段來自于http://www.eccsdk.com/bbs/read.php?tid=1772————————————————
ASN.1 對象標(biāo)識符類型
對象標(biāo)識符(OBJECT IDENTIFIER, OID)類型用層次的形式來表示標(biāo)準(zhǔn)規(guī)范.標(biāo)識符樹通過一個(gè)點(diǎn)分的十進(jìn)制符號來定義,這個(gè)符號以組織,子部分然后是標(biāo)準(zhǔn)的類型和各自的子標(biāo)識符開始.
例如:MD5的OID 是 1.2.840.113549.2.5 表示為"iso(1) member-body (2) US (840) rsadsi(113549) digestAlgorithm (2) md5 (5)", 所以當(dāng)解碼程序看到這個(gè)OID時(shí),就知道是MD5散列.
OID在公鑰算法標(biāo)準(zhǔn)中很流行,它指出證書綁定了哪種散列算法. 同樣,也有公鑰算法,分組算法,和操作模式的OID. 它們是一種高效且可移植的表示數(shù)據(jù)包中所選算法的形式.
對OID的編碼規(guī)則:
1、前兩部分如果定義為x.y, 那么它們將合成一個(gè)字40*x + y, 其余部分單獨(dú)作為一個(gè)字節(jié)進(jìn)行編碼.
2、每個(gè)字首先被分割為最少數(shù)量的沒有頭零數(shù)字的7位數(shù)字.這些數(shù)字以big-endian格式進(jìn)行組織,并且一個(gè)接一個(gè)地組合成字節(jié). 除了編碼的最后一個(gè)字節(jié)外,其他所有字節(jié)的最高位(位8)都為1.
舉例: 30331 = 1 * 128^2 + 108 * 128 + 123 分割成7位數(shù)字(0x80)后為{1,108,123}設(shè)置最高位后變成{129,236,123}.如果該字只有一個(gè)7位數(shù)字,那么最高為0.
MD5 OID的編碼:
1. 將1.2.840.113549.2.5轉(zhuǎn)換成字?jǐn)?shù)組 {42, 840, 113549, 2, 5}.
2. 然后將每個(gè)字分割為帶有最高位的7位數(shù)字,{{0x2A},{0x86,0x48},{0x86,0xF7,0x0D},{0x02},{0x05}}.
3. 最后完整的編碼為 0x06 08 2A 86 48 86 F7 0D 02 05.
————————————————————————————————————————————————————————————
有了上面的直觀的理解,我們再研究代碼就不會(huì)困惑了。
在eSNACC中,OBJECT IDENTIFIER實(shí)現(xiàn)分為oid和RELATIVE OID。oid要求至少必須由兩部分?jǐn)?shù)字組成,因?yàn)橐幋a的一個(gè)值為40*x + y,所以如果不滿足就會(huì)報(bào)錯(cuò)。就如同上面例子中的MD5 OID,就有6個(gè)部分。而RELATIVE OID就沒有這個(gè)要求,對她的編碼沒有做40*x + y的操作,解碼也不需要逆處理。但是RELATIVE OID必須和一個(gè)oid根相關(guān)聯(lián)。因?yàn)?span style="font-family: 'Times New Roman'; font-size: 12pt; mso-fareast-font-family: 宋體; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA" lang="EN-US">RELATIVE OID的定義方式和處理函數(shù)都與oid類似,僅僅是少了上面所說的第一個(gè)編碼規(guī)則操作,所以我們只討論oid,而RELATIVE OID就不展開了。
eSNACC對oid有兩種實(shí)現(xiàn):用字節(jié)串存放和用鏈表形式存放,對字節(jié)串存放,就是每一個(gè)字節(jié)存放一個(gè)數(shù)值;而鏈表,就是每一個(gè)節(jié)點(diǎn)元素存放一個(gè)值。不過文檔說:如果追求更好的性能,應(yīng)當(dāng)采用字節(jié)串的形式。這兩種方式定義如下:
字節(jié)串形式,定義為AsnOid,直接從AsnOcts定義過來:

typedef AsnOcts AsnOid; /**//* standard oid type */
鏈表形式,定義為OID:

/**//* linked oid type that may be easier to use in some circumstances */
#define NULL_OID_ARCNUM -1
typedef struct OID


{
struct OID *next;
long arcNum;
#if COMPILER || TTBL
struct Value *valueRef;
#endif
} OID;
頭文件中還定義了若干函數(shù)實(shí)現(xiàn)這兩者的互相轉(zhuǎn)換。
嚴(yán)重說明:
AsnOid存放的是已經(jīng)對原始的OBJECT IDENTIFIER編碼之后的值!比如上面例子的MD5,字節(jié)串是2A 86 48 86 F7 0D 02 05。
而OID存的是OBJECT IDENTIFIER的原始值鏈。比如上面例子的MD5,鏈表為1->2->840->113549->2->5。
/**************************************休息一下********************************************
上文我們說過:AsnOid存放的是已經(jīng)對原始的OBJECT IDENTIFIER編碼之后的值。所以我們當(dāng)我們要對一個(gè)AsnOid進(jìn)行打印輸出時(shí),就需要進(jìn)行前面所說編碼算法的逆運(yùn)算,這個(gè)我們可以通過打印例程來驗(yàn)證一下:

/**//*
* Prints the given OID to the given FILE * in ASN.1 Value Notation.
* Since the internal rep of an OID is 'encoded', this routine
* decodes each individual arc number to print it.
*/
void
PrintAsnOid PARAMS ((f,v, indent),
FILE *f _AND_
AsnOid *v _AND_
unsigned int indent)


{
unsigned int firstArcNum;
unsigned int arcNum;
int i;

fprintf (f,"{");


/**//* un-munge first two arc numbers */
for (arcNum = 0, i=0; (i < (int)(v->octetLen)) && (v->octs[i] & 0x80);i++)
arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);

arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);
i++;
firstArcNum = (unsigned short)(arcNum/40);
if (firstArcNum > 2)
firstArcNum = 2;

fprintf (f,"%u %u", (unsigned int)firstArcNum, arcNum - (firstArcNum * 40));

for (; i < (int)(v->octetLen); )

{
for (arcNum = 0; (i < (int)(v->octetLen)) && (v->octs[i] & 0x80);i++)
arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);

arcNum = (arcNum << 7) + (v->octs[i] & 0x7f);
i++;
fprintf (f," %u", arcNum);
}
fprintf (f,"}");

indent=indent; /**//* referenced to avoid compiler warning. */


} /**//* PrintAsnOid */
從代碼可以看到,這個(gè)算法就是這樣的:
首先得到第一個(gè)數(shù),因?yàn)槿绻粋€(gè)編碼后的數(shù)用了多個(gè)字節(jié)表示,那么除了最后一個(gè)以外,前面的字節(jié)的最高位肯定為1.所以就用一個(gè)循環(huán)來獲取一個(gè)完整的數(shù)。后面嵌套的for也是這個(gè)原理。然后對取得的第一個(gè)數(shù)分拆:num=first*40+second。這樣就完成第一步的逆算法并打印出來。
然后遍歷這個(gè)字節(jié)串,每獲取一個(gè)完整的數(shù)就打印出來。由于for循環(huán)只是把最高位為1的字節(jié)遍歷了,所以都需要加上最后一個(gè)字節(jié)。其實(shí)我們發(fā)現(xiàn)用多個(gè)字節(jié)存的數(shù)對應(yīng)前面的字節(jié)的128的n次方和最末尾一個(gè)字節(jié)值的和。
而OID存的是原始值,就讓我們通過這個(gè)OID -> AsnOid的函數(shù)來進(jìn)一步理解兩者的不同:

/**//*
* given an oid list and a pre-allocated ENC_OID
* (use EncodedOidLen to figure out byte length needed)
* fills the ENC_OID with a BER encoded version
* of the oid.
*/
void
BuildEncodedOid PARAMS ((oid, result),
OID *oid _AND_
AsnOid *result)


{
unsigned long len;
unsigned long headArcNum;
unsigned long tmpArcNum;
char *buf;
int i;
OID *tmpOid;

buf = result->octs;

/**//*
* oid must have at least 2 elmts
*/
if (oid->next == NULL)
return;

/**//*
* munge together first two arcNum
* note first arcnum must be <= 2
* and second must be < 39 if first = 0 or 1
* see (X.209) for ref to this stupidity
*/
//head = first * 40 + second
headArcNum = (oid->arcNum * 40) + oid->next->arcNum;
tmpArcNum = headArcNum;


/**//*
* 計(jì)算存放第一個(gè)數(shù)需要幾個(gè)字節(jié)。每7位要一個(gè)字節(jié)
*/
for (len = 0; (tmpArcNum >>= 7) != 0; len++)
;


/**//*
* 從高位到低位,把每7位寫到緩沖區(qū),因?yàn)椴皇亲詈笠粋€(gè)字節(jié),所以都把最高位設(shè)為1
*/
for (i=0; i < (int)len; i++)
*(buf++) = (char)(0x80 | (headArcNum >> ((len-i)*7)));


/**//*
* 將寫第一個(gè)數(shù)的最后7位寫到最后一個(gè)字節(jié)
*/
*(buf++) = (char)(0x7f & headArcNum);



/**//*
* 如果有,就把后面的數(shù)寫到緩沖區(qū),原理和第一個(gè)數(shù)相同,里面不再注釋
*/
for (tmpOid = oid->next->next; tmpOid != NULL; tmpOid = tmpOid->next)

{
tmpArcNum = tmpOid->arcNum;
for (len = 0; (tmpArcNum >>= 7) != 0; len++)
;

for (i=0; i < (int)len; i++)
*(buf++) = (char)(0x80 | (tmpOid->arcNum >> ((len-i)*7)));

*(buf++) = (char)(0x7f & tmpOid->arcNum);
}
result->octetLen = (buf - result->octs);//根據(jù)被寫的緩沖區(qū)設(shè)定字節(jié)長度

} /**//* BuildEncodedOid */
在上面的函數(shù)中,我已經(jīng)對相應(yīng)語句做了注釋了,所以這里就不復(fù)述了。
文件中其他函數(shù)都是這個(gè)原理,就很簡單了。本篇就到此吧。