走讀OpenSSL代碼----從一張奇怪的證書說起(三)


本節我們開始查看OpenSSL代碼,我們的目標僅限於找出證書驗證失敗的原因
而不是把整套代碼完全弄懂或精讀,故采用走讀代碼的方式。所用的版本是OpenSSL 0.9.8e
從哪里下手呢?就從其輸出的錯誤信息開始吧。
5840:error:04077068:rsa routines:RSA_verify:bad signature:.\crypto\rsa\rsa_sign.c:235
打開VC工程,在rsa_sign.c文件的235行處下斷點,並設置好命令參數和工作目錄,按F5,程序中斷

View Code
  1 int RSA_verify(int dtype, const unsigned char *m, unsigned int m_len,
2 unsigned char *sigbuf, unsigned int siglen, RSA *rsa)
3 {
4 int i,ret=0,sigtype;
5 unsigned char *s;
6 X509_SIG *sig=NULL;
7
8 if (siglen != (unsigned int)RSA_size(rsa))
9 {
10 RSAerr(RSA_F_RSA_VERIFY,RSA_R_WRONG_SIGNATURE_LENGTH);
11 return(0);
12 }
13
14 if((rsa->flags & RSA_FLAG_SIGN_VER) && rsa->meth->rsa_verify)
15 {
16 return rsa->meth->rsa_verify(dtype, m, m_len,
17 sigbuf, siglen, rsa);
18 }
19
20 s=(unsigned char *)OPENSSL_malloc((unsigned int)siglen);
21 if (s == NULL)
22 {
23 RSAerr(RSA_F_RSA_VERIFY,ERR_R_MALLOC_FAILURE);
24 goto err;
25 }
26 if((dtype == NID_md5_sha1) && (m_len != SSL_SIG_LENGTH) ) {
27 RSAerr(RSA_F_RSA_VERIFY,RSA_R_INVALID_MESSAGE_LENGTH);
28 goto err;
29 }
30 i=RSA_public_decrypt((int)siglen,sigbuf,s,rsa,RSA_PKCS1_PADDING);
31
32 if (i <= 0) goto err;
33
34 /* Special case: SSL signature */
35 if(dtype == NID_md5_sha1) {
36 if((i != SSL_SIG_LENGTH) || memcmp(s, m, SSL_SIG_LENGTH))
37 RSAerr(RSA_F_RSA_VERIFY,RSA_R_BAD_SIGNATURE);
38 else ret = 1;
39 } else {
40 const unsigned char *p=s;
41 sig=d2i_X509_SIG(NULL,&p,(long)i);
42
43 if (sig == NULL) goto err;
44
45 /* Excess data can be used to create forgeries */
46 if(p != s+i)
47 {
48 RSAerr(RSA_F_RSA_VERIFY,RSA_R_BAD_SIGNATURE);
49 goto err;
50 }
51
52 /* Parameters to the signature algorithm can also be used to
53 create forgeries */
54 if(sig->algor->parameter
55 && ASN1_TYPE_get(sig->algor->parameter) != V_ASN1_NULL)
56 {
57 RSAerr(RSA_F_RSA_VERIFY,RSA_R_BAD_SIGNATURE);
58 goto err;
59 }
60
61 sigtype=OBJ_obj2nid(sig->algor->algorithm);
62
63
64 #ifdef RSA_DEBUG
65 /* put a backward compatibility flag in EAY */
66 fprintf(stderr,"in(%s) expect(%s)\n",OBJ_nid2ln(sigtype),
67 OBJ_nid2ln(dtype));
68 #endif
69 if (sigtype != dtype)
70 {
71 if (((dtype == NID_md5) &&
72 (sigtype == NID_md5WithRSAEncryption)) ||
73 ((dtype == NID_md2) &&
74 (sigtype == NID_md2WithRSAEncryption)))
75 {
76 /* ok, we will let it through */
77 #if !defined(OPENSSL_NO_STDIO) && !defined(OPENSSL_SYS_WIN16)
78 fprintf(stderr,"signature has problems, re-make with post SSLeay045\n");
79 #endif
80 }
81 else
82 {
83 RSAerr(RSA_F_RSA_VERIFY,
84 RSA_R_ALGORITHM_MISMATCH);
85 goto err;
86 }
87 }
88 if ( ((unsigned int)sig->digest->length != m_len) ||
89 (memcmp(m,sig->digest->data,m_len) != 0))
90 {
91 RSAerr(RSA_F_RSA_VERIFY,RSA_R_BAD_SIGNATURE);
92 }
93 else
94 ret=1;
95 }
96 err:
97 if (sig != NULL) X509_SIG_free(sig);
98 if (s != NULL)
99 {
100 OPENSSL_cleanse(s,(unsigned int)siglen);
101 OPENSSL_free(s);
102 }
103 return(ret);
104 }

我們只摘抄了部分代碼,源文件中的235行相當於上面的91行(后面的行號都用部分代碼中的顯示行號)
同時給出此時的函數調用棧如下:
> openssl.exe!RSA_verify
  openssl.exe!EVP_VerifyFinal
  openssl.exe!ASN1_item_verify
  openssl.exe!X509_verify
  openssl.exe!internal_verify
  openssl.exe!X509_verify_cert
  openssl.exe!check
  openssl.exe!verify_main
  openssl.exe!do_cmd
  openssl.exe!main
走到91行,是因為觸發了88行的if條件,查看變量,發現 sig->digest->length 與 m_len 相等,都是20
因此推斷是 sig->digest->data 與 m 不相等導致證書驗證錯誤。為什么這兩項不等會導致出錯?
我們回憶下證書驗證的公式
(證書的簽名部分signatureValue)e=SHA1(證書的待簽名部分tbsCertificate)(mod CA的公鑰模)
只有左右兩邊的值相等才認為證書是正常的。
眼前的代碼告訴我們 sig->digest->data 與 m 不相等導致失敗,我們自然猜測
sig->digest->data 與 m 分別表示驗證公式的兩邊部分(只是由於它們的值不等,才導致驗證失敗)
接下來就要確定----sig->digest->data 和 m,誰是驗證公式的左邊項,誰是右邊項。
先看變量 m,是函數RSA_verify的入參,暫時看不出來。
再轉向變量 sig->digest->data,它來源於41行的函數d2i_X509_SIG的入參p
p(來自s)又是從函數RSA_public_decrypt得到(30行),RSA_public_decrypt是干什么的?
從其函數名看,是用RSA公鑰進行“解密”,對於RSA算法,既然用到公鑰,只能是用e進行模冪計算。
到此,我們可以初步判斷,sig->digest->data 應該是公式左邊項,從而 m 應該是公式右邊項。
注意這是判斷,究竟對不對還需要事實驗證(當然后面的結果表明,我們的判斷是正確的)。
用VC的內存查看功能查看 sig->digest->data 的內容
sig->digest->data: 11 12 f6 24 22 52 f0 b8 fd 59 33 1a 1e 5f 50 ca 5a 7c a1 11(十六進制)
這恰恰就是SHA1(tbsCertificate)----不記得的同學請看上一節----說明公式左邊項計算正確。從而
m 代表的右邊公式項可能計算有誤。
繼續追蹤 m,作為函數RSA_verify的入參,m 由調用函數 EVP_VerifyFinal 提供

View Code
 1 int EVP_VerifyFinal(EVP_MD_CTX *ctx, const unsigned char *sigbuf,
2 unsigned int siglen, EVP_PKEY *pkey)
3 {
4 unsigned char m[EVP_MAX_MD_SIZE];
5 unsigned int m_len;
6 int i,ok=0,v;
7 MS_STATIC EVP_MD_CTX tmp_ctx;
8
9 for (i=0; i<4; i++)
10 {
11 v=ctx->digest->required_pkey_type[i];
12 if (v == 0) break;
13 if (pkey->type == v)
14 {
15 ok=1;
16 break;
17 }
18 }
19 if (!ok)
20 {
21 EVPerr(EVP_F_EVP_VERIFYFINAL,EVP_R_WRONG_PUBLIC_KEY_TYPE);
22 return(-1);
23 }
24 EVP_MD_CTX_init(&tmp_ctx);
25 EVP_MD_CTX_copy_ex(&tmp_ctx,ctx);
26 EVP_DigestFinal_ex(&tmp_ctx,&(m[0]),&m_len);
27 EVP_MD_CTX_cleanup(&tmp_ctx);
28 if (ctx->digest->verify == NULL)
29 {
30 EVPerr(EVP_F_EVP_VERIFYFINAL,EVP_R_NO_VERIFY_FUNCTION_CONFIGURED);
31 return(0);
32 }
33
34 return(ctx->digest->verify(ctx->digest->type,m,m_len,
35 sigbuf,siglen,pkey->pkey.ptr));
36 }

34行即是調用 RSA_verify 的地方(以函數指針的方式),m 的內容由 EVP_DigestFinal_ex 函數(26行)得到
EVP_DigestFinal_ex 是摘要計算函數,而25行語句告訴我們,摘要計算的上下文數據結構ctx來自tmp_ctx的復制
換句話說,ctx保存了之前tmp_ctx對某些消息進行摘要計算的中間狀態。
我們再看tmp_ctx,它是函數EVP_VerifyFinal的入參,后者又是由函數ASN1_item_verify調用

View Code
 1 int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *a, ASN1_BIT_STRING *signature,
2 void *asn, EVP_PKEY *pkey)
3 {
4 EVP_MD_CTX ctx;
5 const EVP_MD *type;
6 unsigned char *buf_in=NULL;
7 int ret= -1,i,inl;
8
9 EVP_MD_CTX_init(&ctx);
10 i=OBJ_obj2nid(a->algorithm);
11 type=EVP_get_digestbyname(OBJ_nid2sn(i));
12 if (type == NULL)
13 {
14 ASN1err(ASN1_F_ASN1_ITEM_VERIFY,ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM);
15 goto err;
16 }
17
18 if (!EVP_VerifyInit_ex(&ctx,type, NULL))
19 {
20 ASN1err(ASN1_F_ASN1_ITEM_VERIFY,ERR_R_EVP_LIB);
21 ret=0;
22 goto err;
23 }
24
25 inl = ASN1_item_i2d(asn, &buf_in, it);
26
27 if (buf_in == NULL)
28 {
29 ASN1err(ASN1_F_ASN1_ITEM_VERIFY,ERR_R_MALLOC_FAILURE);
30 goto err;
31 }
32
33 EVP_VerifyUpdate(&ctx,(unsigned char *)buf_in,inl);
34
35 OPENSSL_cleanse(buf_in,(unsigned int)inl);
36 OPENSSL_free(buf_in);
37
38 if (EVP_VerifyFinal(&ctx,(unsigned char *)signature->data,
39 (unsigned int)signature->length,pkey) <= 0)
40 {
41 ASN1err(ASN1_F_ASN1_ITEM_VERIFY,ERR_R_EVP_LIB);
42 ret=0;
43 goto err;
44 }
45 /* we don't need to zero the 'ctx' because we just checked
46 * public information */
47 /* memset(&ctx,0,sizeof(ctx)); */
48 ret=1;
49 err:
50 EVP_MD_CTX_cleanup(&ctx);
51 return(ret);
52 }

果不其然,33行 EVP_VerifyUpdate(&ctx,(unsigned char *)buf_in,inl); 表示進行摘要生成(HASH)操作
buf_in,inl分別是被HASH的內容和長度。
前面我們猜測,m表示驗證公式右側,即SHA1(證書的待簽名部分tbsCertificate),到此處終於得到了印證
我們自然想到,buf_in就是證書的tbsCertificate字段,inl就是tbsCertificate部分的長度。
在調用堆棧窗口雙擊鼠標,切換到ASN1_item_verify的函數棧
再在內存查看窗口輸入buf_in回車,下面是顯示的部分 buf_in 內容(同時可以查看inl為511)
 30 21 30 09 06 05 2b 0e 03 02 1a 05 00 04 14 11  0!0...+.........
 12 f6 24 22 52 f0 b8 fd 59 33 1a 1e 5f 50 ca 5a  ..$"R...Y3.._P.Z
 7c a1 11 cd cd cd cd cd cd cd cd cd cd cd cd cd  |...............
 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
 cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd  ................
 ......
出現了很多字節0xCD,熟悉VC的朋友應該知道,在調試版本中,未初始化的內存空間會被置成0xCD
看起來根本不像證書的tbsCertificate內容,為什么會這樣?難道我們的猜測有誤?
仔細看下代碼,變量buf_in是個指針,它在25行 inl = ASN1_item_i2d(asn, &buf_in, it); 中作為出參
指向的是一個臨時緩沖區(后面的語句 OPENSSL_free(buf_in);已經說明)
而 EVP_VerifyUpdate 正是對這個臨時緩沖區進行HASH操作。
由於當前的執行點已經過了33行(還記得嗎?執行點在斷點處)
所以buf_in所指的區域已經不是當時被HASH的內容,而是別的緩沖區。
我們如何要看到真正的buf_in,很簡單,在33行設置斷點,並重新啟動調試程序
(VC就是方便,看不清楚,可以重新再來),再讓程序斷在這行
buf_in內容如下
 30 82 02 2b a0 03 02 01 02 02 09 00 9d a2 42 4a  0..+..........BJ
 a2 6a 51 de 30 0d 06 09 2a 86 48 86 f7 0d 01 01  .jQ.0...*.H.....
 05 05 00 30 4b 31 0b 30 09 06 03 55 04 06 13 02  ...0K1.0...U....
 43 4e 31 0b 30 09 06 03 55 04 08 13 02 42 4a 31  CN1.0...U....BJ1
 14 30 12 06 03 55 04 0a 13 0b 4e 65 74 53 65 63  .0...U....NetSec
 75 72 69 74 79 31 0c 30 0a 06 03 55 04 0b 13 03  urity1.0...U....
 53 53 4c 31 0b 30 09 06 03 55 04 03 13 02 43 41  SSL1.0...U....CA
 30 1e 17 0d 31 31 31 30 32 39 31 36 33 36 34 37  0...111029163647
 5a 17 0d 31 34 31 30 32 38 31 36 33 36 34 37 5a  Z..141028163647Z
 30 4b 31 0b 30 09 06 03 55 04 06 13 02 43 4e 31  0K1.0...U....CN1
 0b 30 09 06 03 55 04 08 13 02 42 4a 31 14 30 12  .0...U....BJ1.0.
 06 03 55 04 0a 13 0b 4e 65 74 53 65 63 75 72 69  ..U....NetSecuri
 74 79 31 0c 30 0a 06 03 55 04 0b 13 03 53 53 4c  ty1.0...U....SSL
 31 0b 30 09 06 03 55 04 03 13 02 43 41 30 81 9f  1.0...U....CA0..
 ......
 4e 65 74 53 65 63 75 72 69 74 79 31 0c 30 0a 06  NetSecurity1.0..
 03 55 04 0b 13 03 53 53 4c 31 0b 30 09 06 03 55  .U....SSL1.0...U
 04 03 13 02 43 41 82 09 00 9d a2 42 4a a2 6a 51  ....CA.....BJ.jQ
 de 30 0c 06 03 55 1d 13 04 05 30 03 01 01 ff     .0...U....0....
但查看變量inl,長度變成了559,不是當初的511,這是為什么呢?請看下節


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM