分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

本文以x509的解析为例说明asn1的编码格式的解析逻辑。x509证书的解析实际上是asn1格式的解析,这里着重说的是asn1的ber编码的解析,总的来讲,asn1格式的解析过程有三个重要的元素,一个是asn1数据本身,一个是openssl的内部数据结构,比如X509_st,还有一个指导asn1数据往内部数据结构填充的结构体,这个过程实际上就是d2i,而反向的过程就是i2d,asn1作为抽象语法标记语言,ber其实只是其一种编码实现,不管什么实现都要体现“抽象数据结构”本身,这种数据结构其实很显然,标记了类型,标记了数据的长度以及数据本身等,每一种标记都是自解释的,因此最完美的解决方案就是将openssl的内部数据结构也规整为前面所说的这种“抽象数据结构”,也即是说,将内部数据结构作为asn1的另一种编码格式,和ber并列的编码格式,这样就很好理解d2i的过程了,同样i2d是一种和d2i并列的转换,这么理解的话,ber编码和内部编码实质上是同一种意思的两种不同表述,以x509为例,如果现在有一个x509的der格式的数字证书,有一个openssl的x509数据结构,以下的两个数据结构就是这个d2i过程的指导结构:
ASN1_ITEM_st表述一个“项”,这个“项”是一种复合结构,templates是一个ASN1_ITEM_st容器,该容器可以容纳一个ASN1_ITEM_st也可以容纳多个ASN1_ITEM_st,多个ASN1_ITEM_st同样以templates为更低一级的容器,实质上在我们的例子中,x509_cinf结构体就是一个ASN1_ITEM_st,其中包含一系列的ASN1_TEMPLATE,这个一会会谈到:
struct ASN1_ITEM_st {
    char itype;            /* The item type, primitive, SEQUENCE, CHOICE or extern */
    long utype;            /* underlying type */
    const ASN1_TEMPLATE *templates;    //用于itype是SEQUENCE的情况,而一个templates又要包含一个ASN1_ITEM,注释0
    long tcount;            /* Number of templates if SEQUENCE or CHOICE */
    const void *funcs;        /* functions that handle this type */
    long size;            //被描述的内部结构体的大小
};
ASN1_TEMPLATE是ASN1_ITEM的内容容器,真正的内容还是ASN1_ITEM,由于ASN1是一个大的嵌套体,所以每个ASN1_ITEM还可以包含别的ASN1_ITEM,这些ITEM通过TEMPLATE进行汇总,也就是说一个TEMPLATE可以容纳很多存在于TEMPLATE中的ITEM:
struct ASN1_TEMPLATE_st {
    unsigned long flags;        //指示一些特殊用途,比如变长结构或者是可选信息等
    long tag;            //asn.1的tag
    unsigned long offset;        //该TEMPLATE在被描述结构体的偏移
    ASN1_ITEM_EXP *item;        //每个TEMPLATE由一个ASN1_ITEM描述,此字段指向该ITEM
};
以下几个#define是几个帮助宏,学过c语言的人都能看懂:
#define ASN1_ITEM_start(itname) /
        const ASN1_ITEM * itname##_it(void) /
        { /
                static const ASN1_ITEM local_it = { /
#define ASN1_ITEM_end(itname) /
        }; /
        return &local_it; /
        }
#define ASN1_SEQUENCE_ref(tname, cb, lck) /
    static const ASN1_AUX tname##_aux = {NULL, ASN1_AFLG_REFCOUNT, offsetof(tname, references), lck, cb, 0}; /
    ASN1_SEQUENCE(tname)
#define ASN1_SEQUENCE(tname) /
    static const ASN1_TEMPLATE tname##_seq_tt[]
ASN1_SEQUENCE_ref其实就是表示一系列的ASN1_TEMPLATE,也就是一个ASN1_TEMPLAT类型的数组,这些数组就是上面的注释0;
ASN1_SEQUENCE_ref(X509, x509_cb, CRYPTO_LOCK_X509) = { //这里仅仅讲x509,而没有说x509_cinf,但是原理一样。注释1 
    ASN1_SIMPLE(X509, cert_info, X509_CINF),
    ASN1_SIMPLE(X509, sig_alg, X509_ALGOR),
    ASN1_SIMPLE(X509, signature, ASN1_BIT_STRING)
} ASN1_SEQUENCE_END_ref(X509, X509)
#define ASN1_SEQUENCE_END_ref(stname, tname) /
    ;/  //上面ASN1_TEMPLATE数组定义的结束,这种用法“真的很奇妙”
    ASN1_ITEM_start(tname) /
        ASN1_ITYPE_SEQUENCE,/
        V_ASN1_SEQUENCE,/
        tname##_seq_tt,/  //这就是那个ASN1_TEMPLATE数组,到此为止,这个数组是已经定义过的,就在注释1处被定义
        sizeof(tname##_seq_tt) / sizeof(ASN1_TEMPLATE),/
        &tname##_aux,/
        sizeof(stname),/
        #stname /
    ASN1_ITEM_end(tname)
下面的几个宏更能体现出来item和template的意义,openssl中最最难理解的恐怕就是这些宏了,比windows api的宏还可怕,不同的是,windows的api宏主要是为了兼顾兼容性,而openssl的宏主要是为了迎合asn1的简洁结构和实现上的高效,如此复杂的d2i过程在openssl中调用过程竟然没有太深的调用层次,可谓精妙!
#define ASN1_EXP_EX(stname, field, type, tag, ex) /
    ASN1_EX_TYPE(ASN1_TFLG_EXPLICIT | ex, tag, stname, field, type)
#define ASN1_SIMPLE(stname, field, type)     ASN1_EX_TYPE(0,0, stname, field, type)
#define ASN1_EXP_SEQUENCE_OF_OPT(stname, field, type, tag) /
    ASN1_EXP_EX(stname, field, type, tag, ASN1_TFLG_SEQUENCE_OF|ASN1_TFLG_OPTIONAL)
#define ASN1_EXP_EX(stname, field, type, tag, ex) /
    ASN1_EX_TYPE(ASN1_TFLG_EXPLICIT | ex, tag, stname, field, type)
下面的这个宏最终定义了一个ASN1_TEMPLATE,可以看到最后一个字段是ASN1_ITEM_ref(type),实际上ASN1_ITEM_ref也是一个宏,它得到了一个ASN1_ITEM,也就是这个ASN1_TEMPLATE容纳的一个ASN1_ITEM:
#define ASN1_EX_TYPE(flags, tag, stname, field, type) { /
    (flags), (tag), offsetof(stname, field),/
    #field, ASN1_ITEM_ref(type) }
#define ASN1_ITEM_ref(iptr) (&(iptr##_it))
理解了上面的宏和过程之后,接下来看看具体的d2i过程的函数调用过程:
ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **pval, const unsigned char **in, long len, const ASN1_ITEM *it)
{
    ASN1_TLC c;  //初始化一个context,这个context可以保存中间的状态
    ASN1_VALUE *ptmpval = NULL;
    if (!pval)
        pval = &ptmpval;
    c.valid = 0;
    if (ASN1_item_ex_d2i(pval, in, len, it, -1, 0, 0, &c) > 0)
        return *pval;
    return NULL;
}
见下图:

所以说,openssl中的d2i的过程实际上是很简单的,无非就是将具体数据的传输语法在抽象语法的指导下转换为实际语法,上面的例子中只是说明了如何转换为openssl的c语言的内部结构体的过程,但是并不仅仅有这么一种方式,如果你使用的是java,那么应该还有另一套类似的d2i/i2d的代码,转换过程中,i可能有很多,比如c语言的结构体,java语言的类等等,d也可能有很多种(注意仅仅是广义的说,侠义的说,d仅仅指der),不变的2(to)本身,它就是一套指导方案,其实就是asn规范本身,指的就是抽象语法本身,在转换过程中,传输语法和实际语法之间保持高度的一致性,因此出现那么多的递归也就不足为奇了。
     最后看一些openssl中具体代码,这些代码我认为是很值得体会的:
static int asn1_template_noexp_d2i(ASN1_VALUE **val,
                const unsigned char **in, long len,
                const ASN1_TEMPLATE *tt, char opt,
                ASN1_TLC *ctx)
{
    int flags, aclass;
    int ret;
    const unsigned char *p, *q;
    flags = tt->flags;
    aclass = flags & ASN1_TFLG_TAG_CLASS;
    p = *in;
    q = p;
    if (flags & ASN1_TFLG_SK_MASK) {  //如果该项是一个可变大小的数组,比如x509的扩展项
        int sktag, skaclass;
        char sk_eoc;
        ...
        ret = asn1_check_tlen(&len, NULL, NULL, &sk_eoc, NULL,
                    &p, len, sktag, skaclass, opt, ctx);
        else if (ret == -1)
            return -1;
        if (!*val)
            *val = (ASN1_VALUE *)sk_new_null();
        ...
        while(len > 0) {
            ASN1_VALUE *skfield;
            q = p;
            skfield = NULL;
            if (!ASN1_item_ex_d2i(&skfield, &p, len,
                        ASN1_ITEM_ptr(tt->item),
                        -1, 0, 0, ctx))
                ...
            len -= p - q;
            if (!sk_push((STACK *)*val, (char *)skfield)) //val本身就是一个STACK_OF类型的数据结构,是一个变长的内存区域,容纳很多可选项
                ...
        }
            ...
    }
    ...
    else {
        ret = ASN1_item_ex_d2i(val, &p, len, ASN1_ITEM_ptr(tt->item),
                            -1, 0, opt, ctx);
        else if (ret == -1)
            return -1;
    }

*in = p;
    return 1;
}
int ASN1_item_ex_d2i(ASN1_VALUE **pval, const unsigned char **in, long len,
            const ASN1_ITEM *it,
            int tag, int aclass, char opt, ASN1_TLC *ctx)
{
    ...
    switch(it->itype) {
        case ASN1_ITYPE_PRIMITIVE:
        ... //下面的这个asn1_d2i_ex_primitive真正实现了数据的分配和指派
        return asn1_d2i_ex_primitive(pval, in, len, it,....);
        break;
    ...
}
在asn1_check_tlen的调用路径中存在下列代码:
for (i = 0, tt = it->templates; i < it->tcount; i++, tt++)
{
    const ASN1_TEMPLATE *seqtt;
    ASN1_VALUE **pseqval;
    seqtt = asn1_do_adb(pval, tt, 1);
    pseqval = asn1_get_field_ptr(pval, seqtt); //由tt中的offset计算要读取数据在接收结构体里面的偏移
    ...
    else isopt = (char)(seqtt->flags & ASN1_TFLG_OPTIONAL);
    ret = asn1_template_ex_d2i(pseqval, &p, len, seqtt, isopt, ctx);
    ...
    else if (ret == -1) {
        ASN1_template_free(pseqval, seqtt);
        continue;
    }
    len -= p - q;
}
asn1_template_ex_d2i中调用的asn1_check_tlen中可以确定可选项是否存在,形参中ASN1_TLC *ctx是个很重要的参数,它在整个解析过程中起作用,在解析的开始,也就是ASN1_item_d2i中作为局部变量初始化,然后一直到该函数返回后销毁,这个参数在解析过程中存储一些中间状态和临时变量,但凡一些繁琐的冗长的复杂的操作都要有类似的结构体,在linux内核中释放内存时用到的struct scan_control也是类似的结构。在ssl握手的过程中,为了重用消息,或者说为了处理可选消息,用到了struct ssl3_state_st中的tmp字段:
if (s->s3->tmp.message_type == SSL3_MT_SERVER_DONE) { //本次读取的消息和需要读取的不对应,那么暂存起来,下次就不再读取了,而是用这次的。
    s->s3->tmp.reuse_message=1;
    return(1);
}
然后在具体的读取消息函数的最开始:
if (s->s3->tmp.reuse_message) {
    s->s3->tmp.reuse_message=0;
    s->init_msg = s->init_buf->data + 4; //重用了上次读取但是没有使用的消息
    s->init_num = (int)s->s3->tmp.message_size;
    return s->init_num;
}
ASN1_TLC *ctx的用途之一也是这样子的,在asn1_check_tlen中:
if (ctx && ctx->valid) {  //上次读到了,但是没有使用,比如上次是在解析可选项,然而该可选项不存在,于是返回了-1,把读取的结果暂存起来供以后使用(*)
    i = ctx->ret;
    plen = ctx->plen;
    pclass = ctx->pclass;
    ptag = ctx->ptag;
    p += ctx->hdrlen;
} else {
    i = ASN1_get_object(&p, &plen, &ptag, &pclass, len);
    if (ctx) { //保存信息,以便供上面的(*)处使用
...
        ctx->valid = 1;
    }
}
if (exptag >= 0) {
    if ((exptag != ptag) || (expclass != pclass)) {
        if (opt) return -1;
        asn1_tlc_clear(ctx);
        ASN1err(ASN1_F_ASN1_CHECK_TLEN, ASN1_R_WRONG_TAG);
        return 0;
    }
    asn1_tlc_clear(ctx); //清除ctx->valid,代表此次读取的信息被使用了
...
}

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

asn1编码格式的解析过程相关推荐

  1. java解析asn.1编码_asn1编码格式的解析过程

    asn1编码格式的解析过程 本文以x509的解析为例说明asn1的编码格式的解析逻辑.x509证书的解析实际上是asn1格式的解析,这里着重说的是asn1的ber编码的解析,总的来讲,asn1格式的解 ...

  2. Ace2005英文数据解析过程(事件抽取)

    本文是对ace2005-preprocessing代码的解读. 数据集介绍 英文的数据包括以下文件夹:NW(Newswire).BN(Broadcast News).BC(Broadcast Conv ...

  3. DNS解析过程详解【转】

    转自:http://blog.chinaunix.net/uid-28216282-id-3757849.html 先说一下DNS的几个基本概念: 一. 根域 就是所谓的".",其 ...

  4. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  5. oracle 测试sql执行时间_通过错误的SQL来测试推理SQL的解析过程

    这是学习笔记的第 1977 篇文章 如果抛出一个问题,你是如何理解MySQL解析器的,它和Oracle解析器有什么差别?相信大多数同学都会比较迷茫,因为这个问题很难验证,要不是看源码,要不就是查看书上 ...

  6. 【原创】大数据基础之Hive(2)Hive SQL执行过程之SQL解析过程

    Hive SQL解析过程 SQL->AST(Abstract Syntax Tree)->Task(MapRedTask,FetchTask)->QueryPlan(Task集合)- ...

  7. Android init.rc文件解析过程详解(三)

    Android init.rc文件解析过程详解(三) 三.相关结构体 1.listnode listnode结构体用于建立双向链表,这种结构广泛用于kernel代码中, android源代码中定义了l ...

  8. Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  9. Android init.rc文件解析过程详解(一)

        Android init.rc文件解析过程详解(一) 一.init.rc文件结构介绍 init.rc文件基本组成单位是section, section分为三种类型,分别由三个关键字(所谓关键字 ...

最新文章

  1. 解决ecilpse插件安装速度变得很慢
  2. [考试]20150528
  3. mysql增量同步kafka_MySQL数据实时增量同步到Kafka - Flume
  4. 命令行实现SMTP和IMAP
  5. 针对校园某服务器的一次渗透测试
  6. Redis教程:NoSQL键值存储
  7. 腾讯地图api修改信息窗口样式_ThingJS通过地图的信息窗口展示常见数据
  8. 向CentOS6.6服务器安装confluence5.4.4
  9. Exchange 2013CU17和office 365混合部署-设置属性筛选(三)
  10. linux进程栈和线程栈大小,Linux进程栈和线程栈
  11. 关于玩美股期权的一些思路
  12. Python修改证件照底色,get新技能
  13. 山东理工——1019
  14. 计算机专业装win几,老电脑装win7还是win10_老电脑装win10还是win7
  15. excel自动填充脚本(awk)
  16. 你们公司有职业通路图吗
  17. android 手机 p8 GRA-CL00 无法收到组播问题记录
  18. win10+python开发django项目day03
  19. 配置使用costmap_2d_node
  20. mysql daemon failed to start._MySQL Daemon failed to start错误解决办法

热门文章

  1. 15本经典金融投资著作
  2. substr函数c语言实现,substr是什么函数?怎么用
  3. python 线程终止后再启动
  4. 手把手带你 arduino 开发:基于ESP32S 的第一个应用-红外测温枪(带引脚图)
  5. 亳州工业学校计算机,2018亳州市中专讲师助讲专业技术职务评审结果公示
  6. 手把手教你玩多数据源动态切换
  7. CRUD(增删改查)
  8. 数据挖掘02-特征工程良好特征的特点
  9. MATLAB添加工具包(详细)
  10. P1309 [NOIP2011 普及组] 瑞士轮