zoukankan      html  css  js  c++  java
  • 走读OpenSSL代码从一张奇怪的证书说起(八)

    上节末尾,我们提到 d2i_X509 函数,该函数在证书验证过程中的一个调用栈是
        d2i_X509
        d2i_X509_AUX
        PEM_ASN1_read_bio
        PEM_read_bio_X509_AUX
        load_cert
        check
    这是上节中提到的证书验证步骤(1) -- 将证书内容转换为内部结构 -- 的必经之路,但是我们在原始代码中找不到 d2i_X509 的实现过程。

    事实上,包括它在内的一大群函数(最著名的是 d2i/i2d 系列)都在 OpenSSL 中找不到函数定义的源码,下面是双击函数调用栈中 d2i_X509 函数的截图

    上图告诉我们, d2i_X509 函数定义在宏 IMPLEMENT_ASN1_FUNCTIONS(X509) 中,它是如何定义的?
    这个很简单,我们可以通过编译器提供的预处理功能还原其本来的面目。
    VC 中在 cl 编译器后加上 /E 选项就可以得到。下面是宏 IMPLEMENT_ASN1_FUNCTIONS(X509) 进行展开的结果
    X509 *d2i_X509(X509 **a, const unsigned char **in, long len) {
        return (X509 *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, (X509_it()));
    }
    int i2d_X509(X509 *a, unsigned char **out) {
        return ASN1_item_i2d((ASN1_VALUE *)a, out, (X509_it()));
    }
    X509 *X509_new(void) {
        return (X509 *)ASN1_item_new((X509_it()));
    }
    void X509_free(X509 *a) {
        ASN1_item_free((ASN1_VALUE *)a, (X509_it()));
    }
    再用宏展开后的代码替换原文件中的宏定义,就可以满足我们的要求。
    当然,这种手工方式仅仅适用于文件比较少的情况,如果文件有几十、上百个,那就成为一个纯体力活。

    不幸的是, OpenSSL 中恰好充斥了类似下面的各种各样的宏。
    ASN1_SEQUENCE/ASN1_SEQUENCE_END、ASN1_ITEM_TEMPLATE/ASN1_ITEM_TEMPLATE_END、IMPLEMENT_ASN1_FUNCTIONS_fname、IMPLEMENT_ASN1_FUNCTIONS_name
    这些宏定义的代码/数据,在后面的代码走读中经常遇到。

    自然想到,如果能够把“宏定义替换为宏展开后的结果”这一手动过程变成脚本自动执行,那该多好?

    又一次,我们想到 Perl 这个大神,谁要文本处理是它的强项呢?

    不再多说,直接上代码

    View Code
      1 # 名称: auto_expand_macro.pl
      2 # 功能: 将 C 源代码中的指定宏替换为预处理后的结果, 宏在宏定义文件指定, 这些宏在匹配它们的源文件中被替换为预处理后的内容
      3 # 使用方法: perl auto_expand_macro.pl 宏定义文件[全路径] 被处理源文件所在目录[全路径] 编译源文件所在的主目录[全路径]
      4 # 举    例: perl auto_expand_macro.pl d:\openssl-0.9.8e\macro_def.txt d:\openssl-0.9.8e\crypto d:\openssl-0.9.8e
      5 # 实现思路: 对比源文件和预处理后的内容,用宏展开后的内容替换原来的宏定义
      6 # 说明: 读者需要基本掌握 Perl
      7 #       脚本以 quick and dirty 的方式完成
      8 #       适用于 VC2008, 如使用 VC 的其他版本, 请修改相应批处理命令
      9 #       对宏展开后的代码用 uncrustify 进行了美化, 如果不需要可以修改脚本, 将其去掉
     10 #       前提: uncrustify.exe 与配置文件 defaults.cfg 要放在源文件所在的主目录[主编译目录], 请自行下载并存放
     11 #
     12 # 联系: chen_yan_hua@163.com
     13 
     14 use v5.10;
     15 
     16 # 参数检测
     17 if ($#ARGV != 2)
     18 {
     19     say "Usage: perl auto_expand_macro.pl macro_define_file[full_path] sourcefile_dir[full_path] project_dir[full_path]";
     20     exit 0;
     21 }
     22 chdir $ARGV[1] or die "$ARGV[1] : $!\n"; # 检测 宏展开源文件 目录
     23 chdir $ARGV[2] or die "$ARGV[2] : $!\n"; # 检测 编译         主目录
     24 
     25 if (! -e "uncrustify.exe") # 检测 uncrustify.exe
     26 {
     27     say "please confirm uncrustify.exe in $ARGV[2]";
     28     exit 0;
     29 }
     30 if (! -e "defaults.cfg") # 检测 defaults.cfg
     31 {
     32     say "please confirm defaults.cfg in $ARGV[2]";
     33     exit 0;
     34 }
     35 
     36 # 说明: 宏定义文件以 # 开头表示注释, 定义的宏分为两种
     37 #
     38 # 成对宏, 例如
     39 # ASN1_SEQUENCE  ASN1_SEQUENCE_END
     40 #
     41 # 单个宏, 例如
     42 # IMPLEMENT_ASN1_FUNCTIONS
     43 
     44 # 打开宏定义文件, 读入宏到相应数组
     45 $macro_def_file = $ARGV[0];
     46 open(MACRO_DEF_FILE, "<$macro_def_file") or die "unable to open $macro_def_file : $!\n";
     47 while ($line = <MACRO_DEF_FILE>)
     48 {
     49     if ($line =~ /^#/) # 跳过 # 开头的注释
     50     {
     51         next;
     52     }
     53     elsif ($line =~ /^\s*(\w+)\s+(\w+)\s*$/) # 成对宏, 放在 macro_pair_first[]/macro_pair_second[]
     54     {   # 分别保存 开始宏 和 结束宏
     55         $macro_pair_first[$#macro_pair_first+1]   = $1; # ASN1_SEQUENCE
     56         $macro_pair_second[$#macro_pair_second+1] = $2; # ASN1_SEQUENCE_END
     57     }
     58     elsif ($line =~ /^\s*(\w+)\s*$/) # 单个宏, 放在 macro_single_list[]
     59     {
     60         chomp $line;
     61         $macro_single_list[$#macro_single_list+1] = $1;
     62     }
     63 }
     64 close(MACRO_DEF_FILE);
     65 
     66 # 显示读取的宏
     67 say "single MACRO";
     68 say "    ",$_ for @macro_single_list;
     69 say "pair MACRO";
     70 for ($i = 0; $i <= $#macro_pair_first; $i++)
     71 {
     72     say "    [", $macro_pair_first[$i],"\t",$macro_pair_second[$i], "]";
     73 }
     74 
     75 $count = 0;
     76 $temp_file = "temp.file";
     77 
     78 # 递归查找匹配宏的 C 源文件,并在匹配的宏前后加上标记
     79 MarkSourceMacro($ARGV[1]);
     80 
     81 sub MarkSourceMacro
     82 {
     83     my $curdir = $_[0]; # 获取递归的目录
     84     opendir DH, $curdir or die "Can't open directory: $!\n";
     85     my @dirs = readdir DH;
     86     # say qq/Dir: / . $curdir;
     87     foreach my $dir_item (@dirs)
     88     {
     89         if ($dir_item =~ /^(\.|\.\.)$/) # 跳过 . 和 ..
     90         {                               # 不能去掉括号()
     91             next;                       # /^\.|\.\.$/ 的意思是匹配 ^\. 或 \.\.$
     92         }
     93         $full_path = $curdir . "/" . $dir_item;
     94         if (-d $full_path) # 是目录
     95         {
     96             MarkSourceMacro($full_path);
     97         }
     98         else # 是文件
     99         {
    100             if ($dir_item =~ /\.c$/i) # 源文件, 后缀为 .c(.C)
    101             {
    102                 $full_path =~ s{\/}{\\}g;
    103                 match_func($full_path); # 处理匹配的源文件 -- 用 自定义对 标识宏定义
    104             }
    105         }
    106     }
    107     closedir DH;
    108 }
    109 
    110 # MARK 源文件中的指定宏 -- 在其前后加上 MY_MAGIC_MARK_START_XXX/MY_MAGIC_MARK_END_XXX 对
    111 # 指定的宏放在全局变量 macro_single_list macro_pair_first macro_pair_second
    112 sub match_func{
    113     local $/; # $/ 作为局部变量, 用于一次性读出所有内容 -- 跳出作用域后,$/ 将恢复原来的值
    114     my $file = $_[0]; # 以下将源文件全部内容读入变量
    115     open(ORIGIN_SOURCE_FILE, "<$file") or die "unable to open $file : $!\n";
    116     $current_content = <ORIGIN_SOURCE_FILE>;
    117     $origin_content = $current_content;
    118     close(ORIGIN_SOURCE_FILE);
    119 
    120     # MARK 成对宏: 在
    121     #   ASN1_SEQUENCE_ref(X509, x509_cb, CRYPTO_LOCK_X509) = {
    122     #     ASN1_SIMPLE(X509, cert_info, X509_CINF),
    123     #     ASN1_SIMPLE(X509, sig_alg, X509_ALGOR),
    124     #     ASN1_SIMPLE(X509, signature, ASN1_BIT_STRING)
    125     #   } ASN1_SEQUENCE_END_ref(X509, X509)
    126     # 外围包上 MY_MAGIC_MARK_START_XXX/MY_MAGIC_MARK_END_XXX 对, 成为如下格式
    127     #   MY_MAGIC_MARK_START_XXX
    128     #   ASN1_SEQUENCE_ref(X509, x509_cb, CRYPTO_LOCK_X509) = {
    129     #     ......
    130     #   } ASN1_SEQUENCE_END_ref(X509, X509)
    131     #   MY_MAGIC_MARK_END_XXX
    132     for ($i = 0; $i <= $#macro_pair_first; $i++)
    133     {   # 所有成对宏 对源文件处理一次
    134         $start = $macro_pair_first[$i];
    135         $end  = $macro_pair_second[$i];
    136 
    137         $current_content =~ s
    138         {
    139           (           # 捕捉括号开始
    140             $start    # ASN1_SEQUENCE_ref
    141             \(        # ( -- 我们关注的成对宏都有括号(), 这有点 ad hoc 的意味, 至于要关注哪些宏, 取决于具体需求
    142               [^()]*  # 括号内的任意文本
    143             \)        # ) -- 更理想的是匹配嵌套括号对, 参见: <<Perl Cookbook(2nd Edition)>> 6.17 Matching Nested Patterns
    144             .*?       # 任意宏定义体  ASN1_SIMPLE(X509, cert_info, X509_CINF)/ASN1_SIMPLE(X509, sig_alg, X509_ALGOR) ......
    145             $end      # ASN1_SEQUENCE_END_ref
    146             \(        # (
    147               [^()]*  # 括号内的任意文本
    148             \)        # )
    149           )           # 捕捉括号结束
    150         }
    151         {
    152           MY_MAGIC_MARK_START_ .# 字符串 MY_MAGIC_MARK_START_ 与连接符.
    153           ++$count .            # $count 是全局变量,每次的替换值都不同 -- MY_MAGIC_MARK_START_1/2/3/...
    154           "\n$1\n" .            # 匹配的 成对宏定义处 前后用换行符隔开
    155           MY_MAGIC_MARK_END_ .  # MY_MAGIC_MARK_END_ 与连接符.
    156           $count                # 第 $count 个宏定义
    157         }egsx; # e 把 REPLACEMENT 作为 Perl 代码块(而不是一个硬编码的字符串)执行,执行的结果作为替换字串
    158                # g 替换所有
    159                # s 匹配换行
    160                # x 排版美观
    161     }
    162 
    163     # MARK 单个宏: 在
    164     #   IMPLEMENT_ASN1_FUNCTIONS(X509)
    165     # 外围包上 MY_MAGIC_MARK_START_XXX/MY_MAGIC_MARK_END_XXX 对, 成为如下格式
    166     #   MY_MAGIC_MARK_START_XXX
    167     #   IMPLEMENT_ASN1_FUNCTIONS(X509)
    168     #   MY_MAGIC_MARK_END_XXX
    169     for ($i = 0;  $i <= $#macro_single_list; $i++)
    170     {   # 所有单个宏 对源文件处理一次
    171         $start = $macro_single_list[$i];
    172         $current_content =~ s
    173         {
    174           (           # 捕捉括号开始
    175             $start    # IMPLEMENT_ASN1_FUNCTIONS
    176             \(        # ( -- 关注的单个宏都有括号()
    177               [^()]*  # 括号内的任意文本
    178             \)        # )
    179           )           # 捕捉括号结束
    180         }
    181         {
    182           MY_MAGIC_MARK_START_ .# "MY_MAGIC_MARK_START_" .
    183           ++$count .            # 不同的 $count 确保匹配时可以正确定位
    184           "\n$1\n" .            # 匹配的 单个宏定义处 前后用换行符隔开
    185           MY_MAGIC_MARK_END_ .  # "MY_MAGIC_MARK_END_" .
    186           $count                # 第 $count 个宏定义
    187         }egsx;                  # 见上面说明
    188     }
    189 
    190     if( $current_content ne $origin_content ) # 只有真正匹配并发生替换,才保存替换结果
    191     {
    192         open(TEMP_SOURCE_FILE,">$temp_file") or die "unable to open $temp_file : $!\n";
    193         print TEMP_SOURCE_FILE $current_content;
    194         close(TEMP_SOURCE_FILE);      # 替换后结果保存到临时文件
    195         rename $file, $file . ".bak"; # 将源文件备份.bak
    196         rename $temp_file, $file;     # 临时文件覆盖源文件
    197         $macro_match_src_file[$#macro_match_src_file+1] = $file;
    198         say "File: " , $file;
    199     }
    200 }
    201 
    202 # 显示匹配宏的  C 源文件
    203 for ($i = 0; $i <= $#macro_match_src_file; $i++)
    204 {
    205     say "preprocess $macro_match_src_file[$i] ...";
    206 
    207     # 以下适用 openssl 0.9.8e
    208     # 调用编译器对 OpenSSL 源文件进行预处理, 编译选项来自 make_nt_dll_output.txt(又有点 ad hoc), 因为是预处理, 即使编译选项有些偏差, 也不会影响最后结果
    209     $cflag = "-Iinc32 -Itmp32dll.dbg /MDd /Od -DDEBUG -D_DEBUG /W3 /WX /Gs0 /GF /Gy /nologo -DOPENSSL_SYSNAME_WIN32 -DWIN32_LEAN_AND_MEAN -DL_ENDIAN
    210         -DDSO_WIN32 -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE -DOPENSSL_USE_APPLINK -I. /Fdout32dll -DOPENSSL_NO_CAMELLIA -DOPENSSL_NO_RC5
    211         -DOPENSSL_NO_MDC2 -DOPENSSL_NO_KRB5 -DOPENSSL_NO_DYNAMIC_ENGINE -D_WINDLL -DOPENSSL_BUILD_SHLIBCRYPTO";
    212     $cflag =~ s{\n}{}g; # 上面分成多行是为了排版美观, 实际在命令中需要处理成一行
    213 
    214     # 打开 Visual Studio 2008 命令提示窗口, 切换到主编译目录, 执行预处理命令
    215     system('call "C:\Program Files\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" x86' . # 其他 VC 版本请更改为对应的批处理
    216         " && cd $ARGV[2] && cl /E $cflag $macro_match_src_file[$i] > $macro_match_src_file[$i].preprocess");
    217 
    218     update_macro("$macro_match_src_file[$i].preprocess", $macro_match_src_file[$i]); # 用宏展开后结果更新源文件
    219 }
    220 
    221 # 将【MY_MAGIC_MARK_START/END_XXX 对】包围的宏定义替换为预处理(宏展开)后的结果
    222 sub update_macro
    223 {
    224     $src_file = $_[0]; # .preprocess 后缀
    225     $dst_file = $_[1]; # .c/.C 后缀
    226     local $/;
    227     local $temp_file = $dst_file.".tmp";
    228     open(SOURCE_FILE, "<$src_file") or die "unable to open $src_file : $!\n";
    229     $content = <SOURCE_FILE>; # 一次性读入预处理后的源文件
    230     close(SOURCE_FILE);
    231     local %macro_define_list;
    232 
    233     while ( $content =~ /(MY_MAGIC_MARK_START_\d+)(.*?)(MY_MAGIC_MARK_END_\d+)/gs) # 匹配代码块
    234     {
    235         # 对宏的预处理结果进行美化 -- 预处理生成的代码有些 ugly, 原因是宏定义未考虑, 如下
    236         # define IMPLEMENT_ASN1_FUNCTIONS_fname(stname, itname, fname) \
    237         # IMPLEMENT_ASN1_ENCODE_FUNCTIONS_fname(stname, itname, fname) \
    238         # IMPLEMENT_ASN1_ALLOC_FUNCTIONS_fname(stname, itname, fname) // 全部放在一行
    239 
    240         # 本来用下面的一行语句搞定,不知为什么没起作用,期盼指点
    241         # $macro_output = ` echo $2 | uncrustify.exe -q -c defaults.cfg `;
    242         # 所以替换为下面的实现
    243         my $temp_mac_file = "temp_mac_file.txt";
    244         open(TEMP_MACRO_FILE,">$temp_mac_file") or die "unable to open $temp_mac_file : $!\n";
    245         print TEMP_MACRO_FILE $2; # MY_MAGIC_MARK_START/END_XXX 内部的宏展开结果保存到临时文件
    246         close TEMP_MACRO_FILE;
    247 
    248         # 调用 uncrustify 进行代码美化 -- uncrustify 如何使用请自行 google
    249         $macro_output = `uncrustify.exe -q -c defaults.cfg -f $temp_mac_file`;
    250 
    251         $macro_define_list{$1} = $macro_output; # 记录每个 MY_MAGIC_MARK_START/END_XXX 对与宏展开结果的关系
    252     }
    253 
    254     open(DEST_FILE, "<$dst_file") or die "unable to open $dst_file : $!\n";
    255     $content = <DEST_FILE>; # 一次性读入用 MY_MAGIC_MARK_START/END_XXX 标记过的源文件
    256     close(DEST_FILE);
    257 
    258     # 在宏定义处展开,并将原来的宏定义以注释的形式保留
    259     open(TEMP_FILE,">$temp_file") or die "unable to open $temp_file : $!\n";
    260     $content =~ s
    261     {
    262       (MY_MAGIC_MARK_START_\d+) # $1
    263         (.*?)                   # $2
    264       (MY_MAGIC_MARK_END_\d+)
    265     }
    266     {
    267      "#if 0$2#endif" .       # 注释原定义
    268       $macro_define_list{$1} # 根据 MY_MAGIC_MARK_START/END_XXX 对, 输出宏展开结果
    269     }egsx;
    270     print TEMP_FILE $content;
    271     close(TEMP_FILE);
    272 
    273     # 删除 .bak 和 .preprocess 后缀文件
    274     unlink "$dst_file.bak", "$dst_file.preprocess";
    275 
    276     # 用最终结果刷新源文件
    277     rename $temp_file, $dst_file;
    278 }

    脚本 auto_expand_macro.pl 需要与宏定义文件配合使用,下面是笔者用到的一个典型的文件

    # 本文件包含需要自动展开的宏定义,与脚本 auto_expand_macro.pl 配合使用
    # 宏定义共有两种格式,见下面说明
    # 以符号 # 开头的文本是注释内容,脚本将忽略

    # 格式一:成对宏定义
    ASN1_SEQUENCE       ASN1_SEQUENCE_END
    ASN1_CHOICE         ASN1_CHOICE_END
    ASN1_ITEM_TEMPLATE  ASN1_ITEM_TEMPLATE_END
    ASN1_SEQUENCE_ref   ASN1_SEQUENCE_END_ref
    ASN1_SEQUENCE_cb    ASN1_SEQUENCE_END_cb
    ASN1_SEQUENCE_enc   ASN1_SEQUENCE_END_enc
    ASN1_CHOICE         ASN1_CHOICE_END_selector
    ASN1_NDEF_SEQUENCE  ASN1_NDEF_SEQUENCE_END

    # 格式二:单个宏定义
    IMPLEMENT_ASN1_FUNCTIONS_fname
    IMPLEMENT_ASN1_FUNCTIONS_name
    IMPLEMENT_ASN1_TYPE_ex
    IMPLEMENT_ASN1_MSTRING
    IMPLEMENT_ASN1_FUNCTIONS
    IMPLEMENT_ASN1_DUP_FUNCTION
    IMPLEMENT_ASN1_TYPE
    OPENSSL_IMPLEMENT_GLOBAL
    IMPLEMENT_PEM_rw
    IMPLEMENT_EXTERN_ASN1
    IMPLEMENT_ASN1_ENCODE_FUNCTIONS_const_fname

    # IMPLEMENT_STACK_OF 不再用
    # IMPLEMENT_ASN1_SET_OF 不再用

    此外,还需要开源的代码美化工具 uncrustify 及配置文件配合 -- 这不是必选项,可以修改脚本去掉

    使用方法: perl auto_expand_macro.pl 宏定义文件[全路径] 被处理源文件所在目录[全路径] 编译源文件所在的主目录[全路径]
    使用举例: perl auto_expand_macro.pl d:\openssl-0.9.8e\macro_def.txt d:\openssl-0.9.8e\crypto d:\openssl-0.9.8e

    处理后源代码仍可正常编译,并保留了原来的宏定义(与展开后的代码相比较),格式如下
    #if 0
    IMPLEMENT_ASN1_FUNCTIONS(X509)
    #endif
    X509 *d2i_X509(X509 **a, const unsigned char **in, long len) {
        return (X509 *)ASN1_item_d2i((ASN1_VALUE **)a, in, len, (X509_it()));
    }
    int i2d_X509(X509 *a, unsigned char **out) {
        return ASN1_item_i2d((ASN1_VALUE *)a, out, (X509_it()));
    }
    X509 *X509_new(void) {
        return (X509 *)ASN1_item_new((X509_it()));
    }
    void X509_free(X509 *a) {
        ASN1_item_free((ASN1_VALUE *)a, (X509_it()));
    }

    到目前为止,阻碍代码走读的所有外围因素都扫光了。下节起我们开始啃 hard core,可想而知的是,剩下的路仍不平坦。

  • 相关阅读:
    git commit 合并
    git 管理 Linux 文件系统
    python 全局变量的使用
    JavaScript 中 类型转换
    canconfig 配置命令
    python 调用 shell 命令
    python 3 操作mysql数据库的方法
    python 字符串和整数,浮点型互相转换
    JavaScript 里面的整数 位 操作
    JavaScript 使用 php 的变量
  • 原文地址:https://www.cnblogs.com/efzju/p/2651807.html
Copyright © 2011-2022 走看看