走读OpenSSL代码从一张奇怪的证书说起(十一)

尾声

上节中我们已经找到问题的罪魁祸首: ASN1 对 INTEGER 类型的编解码出现问题
google 一下,查到一篇文件《ASN.1/BER/DER 编码子集入门指南》,其中关于 INTEGER 的部分,综述如下

INTEGER 类型表示任意的整数。INTEGER 值可以为正数、负数或 0,具有任意大小,采用简单定长编码
在 X.509 证书中 INTEGER 类型用于表示证书的版本号和序列号
具体编码上,INTEGER 的内容 V 用补码表示(记得TLV格式吗),并使用最少的字节,例如数值 0 编码为一个字节: 0x00
其完整的 ASN1 编码就是 02 01 00,共 3 个字节(TLV)

在我们碰到的问题中,证书序列号是 00 00 a2 42 4a a2 6a 51 df -- 恰恰不是简单定长编码(多了个 00),这就是症结所在
OpenSSL 0.9.8e 版本在对该字段进行编/解码过程中,严格按此规范执行,导致最后验证出错

我们可以再举个例子直接佐证:对问题证书运行 openssl x509 命令(0.9.8e),将其格式由 DER 转换为 PEM
转换得到的证书 Windows 将报“证书可能已损坏,或已被改动。”错误
查看新证书序列号,果然,被截为 00 a2 42 4a a2 6a 51 df
如果运行较新版本的 openssl x509 命令(比如 1.0.0e), 转换后的证书则没有问题

个人认为,作为序列号的 INTEGER, 不必考虑在数值上的正负,事实上,只要考虑其编码的二进制表示就可以。
在 0.9.8.e 版本中,这个简单的道理没有被采纳,反而在序列号的 INTEGER 编/解码上弄得很复杂,最终栽了跟头。

为什么 OpenSSL 1.0.0e 可以正常处理具有特殊序列号的证书?
其实思路很简单 -- Encode 为 DER 时不再折腾,直接使用先前 Decode 时保存的 DER 编码(来自文件)
下面我们简单讨论下 1.0.0e 版本的实现,与 0.9.8e 版本相比,它在结构 X509_CINF 中新增成员 enc, 如下

View Code
 1 typedef struct x509_cinf_st
 2 {
 3     ASN1_INTEGER *version;       /* [ 0 ] default of v1 */
 4     ASN1_INTEGER *serialNumber;
 5     X509_ALGOR *signature;
 6     X509_NAME *issuer;
 7     X509_VAL *validity;
 8     X509_NAME *subject;
 9     X509_PUBKEY *key;
10     ASN1_BIT_STRING *issuerUID;  /* [ 1 ] optional in v2 */
11     ASN1_BIT_STRING *subjectUID; /* [ 2 ] optional in v2 */
12     STACK_OF(X509_EXTENSION) *extensions; /* [ 3 ] optional in v3 */
13     ASN1_ENCODING enc; // 1.0.0e 版本中新增
14 } X509_CINF;
15 
16 typedef struct ASN1_ENCODING_st
17 {
18     unsigned char *enc; /* DER encoding */ -- DER 编码缓冲区
19     long len;           /* Length of encoding */
20     int modified;       /* set to 1 if 'enc' is invalid */
21 } ASN1_ENCODING;

程序在 d2i_X509 函数中调用 asn1_enc_save 函数, 后者会直接保存证书信息(tbsCertificate 部分)的 DER 编码,如下

View Code
 1 int asn1_enc_save(ASN1_VALUE **pval, const unsigned char *in, int inlen,
 2                   const ASN1_ITEM *it) // it 指向 X509_CINF_it 中返回的静态变量,见下面
 3 {
 4   ASN1_ENCODING *enc;
 5   enc = asn1_get_enc_ptr(pval, it);
 6   static ASN1_ENCODING *asn1_get_enc_ptr(ASN1_VALUE **pval, const ASN1_ITEM *it)
 7   {
 8     const ASN1_AUX *aux;
 9     if (!pval || !*pval)
10       return NULL;
11 
12     aux = it->funcs;
13     // 0.9.8e 版本中 funcs 成员指向 0
14     // 1.0.0e 版本中则指向 X509_CINF_aux(新增变量, 不再列出, 请查看定义), 见下面的对比
15     // const ASN1_ITEM * X509_CINF_it(void) {
16     //   static const ASN1_ITEM local_it = {
17     //     0x1, 16, X509_CINF_seq_tt, sizeof(X509_CINF_seq_tt) / sizeof(ASN1_TEMPLATE),
18     //     0/&X509_CINF_aux, sizeof(X509_CINF), "X509_CINF"
19     //   };
20     //   return &local_it;
21     // }
22 
23     if (!aux || !(aux->flags & ASN1_AFLG_ENCODING))
24       return NULL; // 0.9.8e 从此处返回 -- aux == 0
25     return offset2ptr(*pval, aux->enc_offset); // 1.0.0e 返回 X509_CINF.enc 成员的地址
26   }
27 
28   if (!enc) // 0.9.8e: 返回 -- enc == 0, 后续的 asn1_template_ex_d2i() 在调用 asn1_ex_c2i 时出错
29     return 1;
30 
31   // 1.0.0e: 走到此处
32   // 直接保存 DER 编码到 enc->enc 缓冲区, DER 编码在后面的 asn1_enc_restore 中恢复
33   // 对于证书验证而言,涉及的 DER 编码是 tbsCertificate 部分,对应的数据结构是 X509_CINF
34   if (enc->enc)
35     OPENSSL_free(enc->enc);
36   enc->enc = OPENSSL_malloc(inlen);
37   if (!enc->enc)
38     return 0;
39   memcpy(enc->enc, in, inlen);
40   enc->len = inlen;
41   enc->modified = 0;
42 
43   return 1;
44 }

下面是保存证书 DER 编码的堆栈
  > asn1_enc_save
    ASN1_item_ex_d2i
    asn1_template_noexp_d2i
    asn1_template_ex_d2i
    ASN1_item_ex_d2i
    ASN1_item_d2i
    d2i_X509

下面是恢复证书 DER 编码的堆栈
  > asn1_enc_restore
    ASN1_item_ex_i2d
    asn1_item_flags_i2d
    ASN1_item_i2d
    ASN1_item_verify
    X509_verify

有兴趣的读者可以去查看代码实现,并追踪调试。

最后,回答一下我们在前面提出的问题:这个证书是怎么得到的?

事实上,这是笔者在 N 年前碰到的一个问题,当时碰到一张证书,其序列号以两个连续的 0x00 开头
刚好用 OpenSSL 0.9.8e 验证,遇到签名失败提示(幸好当时没出 1.0.0e 版本,否则就看不到这个系列了:-/ )

现在完成的这个系列,算是纪念一下那段经历吧。

这张问题证书,就是在前期“从数学到密码学”系列中的证书 sslclientcert 基础上, 手工修改了序列号的一个字节,即
把 00 9d a2 42 4a a2 6a 51 df 改为 00 00 a2 42 4a a2 6a 51 df, 然后再计算新的签名值,最后拼装得到的一个手工打造版证书。

在解决每一个具体问题的过程中,都应该用心去寻找扩展为一类问题的通用“解决方案”。
这也是每个真正的程序员所应该追求的。

至此,本系列全部结束。

原文地址:https://www.cnblogs.com/efzju/p/2676123.html