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,可想而知的是,剩下的路仍不平坦。

  • 相关阅读:
    Vsftpd 3.0.2 正式版发布
    Putdb WebBuilder 6.5 正式版本发布
    SoaBox 1.1.6 GA 发布,SOA 模拟环境
    pynag 0.4.6 发布,Nagios配置和插件管理
    Percona Playback 0.4,MySQL 负荷回放工具
    xombrero 1.3.1 发布,微型 Web 浏览器
    Hypertable 0.9.6.4 发布,分布式数据库
    libmemcached 1.0.11 发布
    CryptoHeaven 3.7 发布,安全邮件解决方案
    Android Activity生命周期
  • 原文地址:https://www.cnblogs.com/efzju/p/2651807.html
Copyright © 2011-2022 走看看