eSNACC对OBJECT IDENTIFIER的编码和解码

eSNACC对OBJECT IDENTIFIER的编码和解码

本文剖析asn-oid.h/c,从源代码来学习eSNACC对OBJECT IDENTIFIER的编码和解码。

在研究代码之前,我们先来说明什么是OBJECT IDENTIFIER。

————————————以下一段来自于http://www.eccsdk.com/bbs/read.php?tid=1772————————————————

 ASN.1 对象标识符类型 
      对象标识符(OBJECT IDENTIFIER, OID)类型用层次的形式来表示标准规范.标识符树通过一个点分的十进制符号来定义,这个符号以组织,子部分然后是标准的类型和各自的子标识符开始. 

      例如:MD5的OID 是 1.2.840.113549.2.5  表示为"iso(1) member-body (2) US (840) rsadsi(113549) digestAlgorithm (2) md5 (5)", 所以当解码程序看到这个OID时,就知道是MD5散列. 

      OID在公钥算法标准中很流行,它指出证书绑定了哪种散列算法. 同样,也有公钥算法,分组算法,和操作模式的OID. 它们是一种高效且可移植的表示数据包中所选算法的形式. 

      对OID的编码规则: 
1、前两部分如果定义为x.y, 那么它们将合成一个字40*x + y, 其余部分单独作为一个字节进行编码. 
2、每个字首先被分割为最少数量的没有头零数字的7位数字.这些数字以big-endian格式进行组织,并且一个接一个地组合成字节. 除了编码的最后一个字节外,其他所有字节的最高位(位8)都为1.
     举例: 30331 = 1 * 128^2 + 108 * 128 + 123  分割成7位数字(0x80)后为{1,108,123}设置最高位后变成{129,236,123}.如果该字只有一个7位数字,那么最高为0.   

     MD5 OID的编码: 
        1. 将1.2.840.113549.2.5转换成字数组 {42, 840, 113549, 2, 5}. 
        2. 然后将每个字分割为带有最高位的7位数字,{{0x2A},{0x86,0x48},{0x86,0xF7,0x0D},{0x02},{0x05}}. 
        3. 最后完整的编码为 0x06 08 2A 86 48 86 F7 0D 02 05.

————————————————————————————————————————————————————————————

有了上面的直观的理解,我们再研究代码就不会困惑了。

在eSNACC中,OBJECT IDENTIFIER实现分为oid和RELATIVE OID。oid要求至少必须由两部分数字组成,因为要编码的一个值为40*x + y,所以如果不满足就会报错。就如同上面例子中的MD5 OID,就有6个部分。而RELATIVE OID就没有这个要求,对她的编码没有做40*x + y的操作,解码也不需要逆处理。但是RELATIVE OID必须和一个oid根相关联。因为RELATIVE OID的定义方式和处理函数都与oid类似,仅仅是少了上面所说的第一个编码规则操作,所以我们只讨论oid,而RELATIVE OID就不展开了。

eSNACC对oid有两种实现:用字节串存放和用链表形式存放,对字节串存放,就是每一个字节存放一个数值;而链表,就是每一个节点元素存放一个值。不过文档说:如果追求更好的性能,应当采用字节串的形式。这两种方式定义如下:

字节串形式,定义为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;

头文件中还定义了若干函数实现这两者的互相转换。

严重说明:

AsnOid存放的是已经对原始的OBJECT IDENTIFIER编码之后的值!比如上面例子的MD5,字节串是2A 86 48 86 F7 0D 02 05。

而OID存的是OBJECT IDENTIFIER的原始值链。比如上面例子的MD5,链表为1->2->840->113549->2->5。

 

 

/**************************************休息一下********************************************

上文我们说过:AsnOid存放的是已经对原始的OBJECT IDENTIFIER编码之后的值。所以我们当我们要对一个AsnOid进行打印输出时,就需要进行前面所说编码算法的逆运算,这个我们可以通过打印例程来验证一下:

/**/ /*
 * 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 */

从代码可以看到,这个算法就是这样的:

首先得到第一个数,因为如果一个编码后的数用了多个字节表示,那么除了最后一个以外,前面的字节的最高位肯定为1.所以就用一个循环来获取一个完整的数。后面嵌套的for也是这个原理。然后对取得的第一个数分拆:num=first*40+second。这样就完成第一步的逆算法并打印出来。

然后遍历这个字节串,每获取一个完整的数就打印出来。由于for循环只是把最高位为1的字节遍历了,所以都需要加上最后一个字节。其实我们发现用多个字节存的数对应前面的字节的128的n次方和最末尾一个字节值的和。

 

而OID存的是原始值,就让我们通过这个OID -> AsnOid的函数来进一步理解两者的不同:

/**/ /*
 * 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;

    
/**//*
     * 计算存放第一个数需要几个字节。每7位要一个字节
     
*/

    
for (len = 0; (tmpArcNum >>= 7!= 0; len++)
    ;

    
/**//*
     * 从高位到低位,把每7位写到缓冲区,因为不是最后一个字节,所以都把最高位设为1
     
*/

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

    
/**//*
     * 将写第一个数的最后7位写到最后一个字节
     
*/

    
*(buf++= (char)(0x7f & headArcNum);


    
/**//*
     * 如果有,就把后面的数写到缓冲区,原理和第一个数相同,里面不再注释
     
*/

    
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);//根据被写的缓冲区设定字节长度
}
  /**/ /* BuildEncodedOid */

在上面的函数中,我已经对相应语句做了注释了,所以这里就不复述了。

 

文件中其他函数都是这个原理,就很简单了。本篇就到此吧。

你可能感兴趣的:(eSNACC对OBJECT IDENTIFIER的编码和解码)