您好,登錄后才能下訂單哦!
在OpenSSL心臟出血之后,我相信很多人都出了血,并且流了淚...網(wǎng)上瞬間出現(xiàn)了大量吐嘈OpenSSL的文章或段子,仿佛內(nèi)心的窩火一瞬間被釋放了出來(lái),跟著這場(chǎng)瘋鬧,我也吐一下嘈,以雪這些年被OpenSSL蹂躪之辱,也許可以順便展現(xiàn)一下我的無(wú)知與愚昧,但只是也許...
首先聲明的一點(diǎn)是,我并沒(méi)有惡意詆毀的意思,也并沒(méi)有針對(duì)什么,比起生活中的大喜大悲,比起工作中的大起大落,比起追求理想過(guò)程中的遭遇坎坷,OpenSSL的折磨其實(shí)是一種幸福,只是對(duì)幸福的解讀,有時(shí)可以認(rèn)為是,痛并快樂(lè)著,齊秦如是說(shuō)...
OpenSSL代碼真的很爛,太爛,毫無(wú)章法的亂。
實(shí)用主義者,或者中毒已深的人總是能給出一段代碼之所以這么寫(xiě)而不那么寫(xiě)的理由,并且理由還特別充分,以至于你也會(huì)認(rèn)為這么寫(xiě),寫(xiě)成這么爛是有理由的,其中一定藏著什么不易理解的玄機(jī),但是,作為非神學(xué)的世俗作品,它不是圣經(jīng),不易理解本身就是一個(gè)過(guò)錯(cuò),當(dāng)然,也許是我水平太水太菜,沒(méi)有達(dá)到OpenSSL要求的那種深度,如果這樣,這篇吐嘈就是寫(xiě)給和我相同水平的菜鳥(niǎo)看的,高手請(qǐng)默默離開(kāi),不要帶走一點(diǎn)悲哀,留下的這些悲哀,讓我們這些菜鳥(niǎo)的眼淚洗刷刷吧...存在就是合理的,好吧,西西弗斯的神話(huà)表示人生就是一場(chǎng)悲哀,收成抵不上成本,它是存在的,因此是合理的,請(qǐng)不要報(bào)怨OpenSSL,它也是合理的,是的,完全正確。
開(kāi)源是偉大的,至少曾經(jīng)是偉大的,質(zhì)疑它的人,一定沒(méi)有體驗(yàn)過(guò)Linus Torvalds爆米且口的那份激動(dòng)與聽(tīng)眾受虐般的激情,也不一定擁有站在Richard Stallman或者極端的Eric S. Raymond腳下的那份敬畏和感動(dòng)。但是OpenSSL出現(xiàn)以后,表明開(kāi)源所表達(dá)的自由還有另外一層意思,那就是代碼擁有不受審查的自由,有爛的自由,更多的,每個(gè)人都有使用爛代碼的自由,更進(jìn)一步的,每個(gè)人都有把爛代碼說(shuō)成藝術(shù)的自由,而這份自由,被OpenSSL那黑翼般的力量煽動(dòng),帶給了每一個(gè)人,于是,心臟流血的時(shí)候,我攥起了拳頭...
說(shuō)多了都是淚...突然看到了一個(gè)項(xiàng)目,OpenBSD發(fā)起一個(gè)清理OpenSSL代碼的項(xiàng)目,就想繼續(xù)淚下去,等看完我這篇吐嘈,請(qǐng)帶著淚去欣賞吧,鏈接在下面:
清爽鏈接1
清爽鏈接2
同樣值得欣賞的是,Open×××的代碼,同樣狠爛!欣賞鏈接之前,請(qǐng)讓我拋塊磚,來(lái)點(diǎn)小菜。我們開(kāi)始吧!{ if () return ret; else if () return ret2; }這樣合理嗎?代碼當(dāng)然是正確的,但是不明朗,不光人看得不明朗,有些編譯器也會(huì)抱怨...OpenSSL中大量這種代碼,悲哀的是,還不是OpenSSL的全部代碼都這樣!
for (...) { if () { flag = 1; } ... if (flag2 == 2) { flag = 2; } ... } if (flag == 3 || flag2 == 1) { ... }我曾經(jīng)及其痛苦地在魔術(shù)字和flags之間進(jìn)行選擇,因?yàn)槲襎MD根本就不懂軟件開(kāi)發(fā),我天真地以為軟件開(kāi)發(fā)就是編程,就是讓代碼跑起來(lái),直到我看到了OpenSSL,發(fā)現(xiàn)軟件開(kāi)發(fā)要做的就是讓代碼跑起來(lái)這么簡(jiǎn)單!!OpenSSL就能跑起來(lái)!前面說(shuō)了,OpenSSL定義了太多的變量,但是卻還不夠多,因?yàn)榈教帟?huì)出現(xiàn)if (var == 2),var2=3,var3 < 5,之類(lèi)的代碼,2,3,5代表什么意思呢?OpenSSL的注釋同樣很多,但是還不夠多,該有的注釋沒(méi)有,晦澀的地方一般都是jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
some_function(...) { ... return(n); } /* If we get here, then type != rr->type; if we have a handshake * message, then it was unexpected (Hello Request or Client Hello). */ /* In case of record types for which we have 'fragment' storage, * fill that so that we can process the data at a fixed place. */ { unsigned int dest_maxlen = 0; unsigned char *dest = NULL; unsigned int *dest_len = NULL; if (rr->type == SSL3_RT_HANDSHAKE) { dest_maxlen = sizeof s->s3->handshake_fragment; dest = s->s3->handshake_fragment; dest_len = &s->s3->handshake_fragment_len; } ... }在函數(shù)中間夾了一個(gè)塊,夾得緊緊的,舒服嗎?可能是因?yàn)樽髡呤褂昧瞬煌腃標(biāo)準(zhǔn),又想聲明新的變量,又不想動(dòng)原來(lái)的代碼,不加新塊又編譯不過(guò),只好這么玩了...但只是可能而已,事實(shí)上作者可能根本就沒(méi)有想這么多,我個(gè)人也喜歡這么干,有時(shí)我的想法是嘗試一個(gè)新點(diǎn)子,如果不行的話(huà)又方便恢復(fù)成原來(lái)的,又討厭使用宏,主要是打字成本太高了,事實(shí)上直到不久之前,我才知道在一個(gè)塊中,變量聲明的位置并不能是隨意的,當(dāng)然,標(biāo)準(zhǔn)不同,限制也不同...
#define ARGV Argv int main(int Argc, char *ARGV[])這么做的藝術(shù)性何在?我將繼續(xù)苦逼地上下而求索。
#if 0 /* worked only because C operator preferences are not as expected (and * because this is not really needed for clients except for detecting * protocol violations): */ s->state=SSL_ST_BEFORE|(s->server) ?SSL_ST_ACCEPT :SSL_ST_CONNECT; #else s->state = s->server ? SSL_ST_ACCEPT : SSL_ST_CONNECT; #endif注意上面注釋第一行的那個(gè)“(and”,看得出是作者故意這么做的,以表現(xiàn)一下自己的立體主義??
int func() { ... a=b; c = d; }如果我寫(xiě)出這種代碼,又要被罵了,但是慢慢的,我不覺(jué)得因此被罵是一種讓人痛苦的事,就像OpenSSL一樣將自虐當(dāng)成了快感的來(lái)源!
some_function(...) { ... if (s->session->sess_cert != NULL) { #ifndef OPENSSL_NO_RSA if (s->session->sess_cert->peer_rsa_tmp != NULL) { ... } #endif ... } else { ...; } ... #ifndef OPENSSL_NO_RSA if (alg & SSL_kRSA) { ... } #else /* OPENSSL_NO_RSA */ if (0) ; #endif #ifndef OPENSSL_NO_DH else if (alg & SSL_kEDH) { ... #ifndef OPENSSL_NO_RSA if (alg & SSL_aRSA) ... #else if (0) ; #endif #ifndef OPENSSL_NO_DSA else if (alg & SSL_aDSS) ...; #endif /* else anonymous DH, so no certificate or pkey. */ ... } else if ((alg & SSL_kDHr) || (alg & SSL_kDHd)) { ... goto f_err; } #endif /* !OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH else if (alg & SSL_kECDHE) { ... if (0) ; #ifndef OPENSSL_NO_RSA else if (alg & SSL_aRSA) ...; #endif #ifndef OPENSSL_NO_ECDSA else if (alg & SSL_aECDSA) ...; #endif /* else anonymous ECDH, so no certificate or pkey. */ ... } else if (alg & SSL_kECDH) { ... goto f_err; } #endif /* !OPENSSL_NO_ECDH */ if (alg & SSL_aFZA) { ... goto f_err; } /* p points to the next byte, there are 'n' bytes left */ /* if it was signed, check the signature */ if (pkey != NULL) { ... if ((i != n) || (n > j) || (n <= 0)) { /* wrong packet length */ ... goto f_err; } #ifndef OPENSSL_NO_RSA if (pkey->type == EVP_PKEY_RSA) { ... for (num=2; num > 0; num--) { ... } ... if (i < 0) { ... goto f_err; } if (i == 0) { /* bad signature */ ... goto f_err; } } else #endif #ifndef OPENSSL_NO_DSA if (pkey->type == EVP_PKEY_DSA) { /* lets do DSS */ ... if (EVP_VerifyFinal(&md_ctx,p,(int)n,pkey) <= 0) { /* bad signature */ ... goto f_err; } } else #endif #ifndef OPENSSL_NO_ECDSA if (pkey->type == EVP_PKEY_EC) { /* let's do ECDSA */ ... if (EVP_VerifyFinal(&md_ctx,p,(int)n,pkey) <= 0) { /* bad signature */ ... goto f_err; } } else #endif { ... goto err; } } else { /* still data left over */ if (!(alg & SSL_aNULL)) { ... goto err; } if (n != 0) { ... goto f_err; } } ... return(1); f_err: ...; err: ...; #ifndef OPENSSL_NO_RSA if (rsa != NULL) RSA_free(rsa); #endif #ifndef OPENSSL_NO_DH if (dh != NULL) DH_free(dh); #endif #ifndef OPENSSL_NO_ECDH ...; if (ecdh != NULL) EC_KEY_free(ecdh); #endif ...; return(-1); }代碼是有點(diǎn)長(zhǎng)了,但是實(shí)際的代碼就是如此!簡(jiǎn)直就是宏的地獄,if (0)這種代碼的目的就是為了膠合諸多宏之間的互斥關(guān)系,讓互斥代碼的某部分不執(zhí)行??唉,宏與宏之間發(fā)生了關(guān)系,你就不再是C編程,而是宏編程...話(huà)說(shuō),上述的代碼實(shí)際上是一個(gè)不含業(yè)務(wù)的邏輯框架,就像鋼混框架結(jié)構(gòu)建筑的那個(gè)大架子一樣,和IOCCC獲獎(jiǎng)代碼還是天上地下的,真正的IOCCC代碼是無(wú)框架的,框架隱藏于的業(yè)務(wù)本身,它的美感類(lèi)似于類(lèi)似海洋軟體動(dòng)物的那種美。
if(!ok) goto end; if (0) { end: X509_get_pubkey_parameters(NULL,ctx->chain); }事實(shí)上想玩好if (0)只有兩種方法,第一就是使用宏把if (0)屏蔽掉,第二就是使用goto把if (0)強(qiáng)暴掉,不過(guò)還有一種方式,把0的意義改掉。大量的#if 0,#if 1,if (0), if (1)的存在,外加一些令人看到“世界在進(jìn)步”的注釋?zhuān)瑢penSSL變成了一座僵尸博物館,這些永遠(yuǎn)都不會(huì)被執(zhí)行到的代碼旁邊都會(huì)有一些個(gè)注釋?zhuān)忈屩鼈冊(cè)?jīng)的光輝和日前為何變成了木乃伊。可是為何不把它們直接刪掉呢?既然已經(jīng)知道了它們已然無(wú)用并且知道了為什么已然無(wú)用,還留著它們,我想作者們都是些懷舊之士吧。這使我們這些后來(lái)人在讀代碼或者改代碼的時(shí)候不得不先預(yù)處理一遍。對(duì)于我個(gè)人來(lái)講,我不喜歡預(yù)處理,我直接手工刪掉那些永不被執(zhí)行的代碼,我甚至將此事作為當(dāng)成一種無(wú)聊時(shí)的消遣,和展Windows注冊(cè)表一展一下午一樣獲得一種升華意義的快感!我真的曾經(jīng)展過(guò)注冊(cè)表,展了一下午都沒(méi)有展完...
do { ... if (...) break; ... }while(0);我因這種代碼而被罵狗屎,不過(guò)當(dāng)時(shí)我并沒(méi)有生氣,反而和另一個(gè)同事在旁邊偷笑,聽(tīng)說(shuō),笑能長(zhǎng)壽,看來(lái)以后要多看看OpenSSL的代碼了。
int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len) { ... n=(len-tot); for (;;) { if (n > SSL3_RT_MAX_PLAIN_LENGTH) nw=SSL3_RT_MAX_PLAIN_LENGTH; else nw=n; // 我覺(jué)得這是個(gè)核心函數(shù) i=do_ssl3_write(s, type, &(buf[tot]), nw, 0); if (i <= 0) { s->s3->wnum=tot; return i; } if ((i == (int)n) || (type == SSL3_RT_APPLICATION_DATA && (s->mode & SSL_MODE_ENABLE_PARTIAL_WRITE))) { /* next chunk of data should get another prepended empty fragment * in ciphersuites with known-IV weakness: */ s->s3->empty_fragment_done = 0; return tot+i; } n-=i; tot+=i; } }看到這段代碼,一般人會(huì)怎么想?當(dāng)然深深中了OpenSSL邪毒的那幫人不屬于一般人。一般人看了會(huì)覺(jué)得,一個(gè)buff可能會(huì)分為多次發(fā)送,所以有了一個(gè)for(;;),直到發(fā)送完為止,如果接口行為定義良好,我應(yīng)該放棄希望了,因?yàn)榘凑找陨纤膶?shí)現(xiàn)邏輯,一個(gè)buff可能會(huì)被分割為多段,每段調(diào)用do_ssl3_write發(fā)送,這樣一個(gè)buff就會(huì)形成多個(gè)record,從而打破了我的幻想,此時(shí)我想哭,因?yàn)槲也坏貌辉俅稳ゲ偌一飻嚬肥?,噢,多么痛的領(lǐng)悟,多么直白的坦言。
static int do_ssl3_write(SSL *s, int type, const unsigned char *buf, unsigned int len, int create_empty_fragment) { unsigned char *p,*plen; int i,mac_size,clear=0; int prefix_len = 0; SSL3_RECORD *wr; SSL3_BUFFER *wb; SSL_SESSION *sess; /* first check if there is a SSL3_BUFFER still being written * out. This will happen with non blocking IO */ if (s->s3->wbuf.left != 0) // 在一開(kāi)始的位置,處理邏輯就被劫持了,因此我就必須注意left在什么情況下不為0 // 這個(gè)執(zhí)行流跳轉(zhuǎn)得很詭異!太詭異! return(ssl3_write_pending(s,type,buf,len)); /* If we have an alert to send, lets send it */ if (s->s3->alert_dispatch) { i=s->method->ssl_dispatch_alert(s); if (i <= 0) return(i); /* if it went, fall through and send more stuff */ } // create_empty_fragment?難道還有不這樣做的?Fxxxing,在上層調(diào)用的時(shí)候,這個(gè)參數(shù)為0,這就意味著 // 肯定有什么地方以1為參數(shù)調(diào)用了本函數(shù)。這個(gè)empty fragment我后面會(huì)解釋。 if (len == 0 && !create_empty_fragment) return 0; wr= &(s->s3->wrec); wb= &(s->s3->wbuf); sess=s->session; ... if (clear) mac_size=0; else mac_size=EVP_MD_size(s->write_hash); /* 'create_empty_fragment' is true only when this function calls itself */ if (!clear && !create_empty_fragment && !s->s3->empty_fragment_done) { /* countermeasure against known-IV weakness in CBC ciphersuites * (see http://www.openssl.org/~bodo/tls-cbc.txt) */ if (s->s3->need_empty_fragments && type == SSL3_RT_APPLICATION_DATA) { /* recursive function call with 'create_empty_fragment' set; * this prepares and buffers the data for an empty fragment * (these 'prefix_len' bytes are sent out later * together with the actual payload) */ // 遞歸調(diào)用?我kao,這個(gè)函數(shù)竟然有兩段邏輯: // 1.默默創(chuàng)建一個(gè)新的record; // 2.創(chuàng)建封裝buf的record并和遞歸調(diào)用中默默創(chuàng)建的那個(gè)record一起發(fā)送 prefix_len = do_ssl3_write(s, type, buf, 0, 1); if (prefix_len <= 0) goto err; if (s->s3->wbuf.len < (size_t)prefix_len + SSL3_RT_MAX_PACKET_SIZE) { /* insufficient space */ SSLerr(SSL_F_DO_SSL3_WRITE, ERR_R_INTERNAL_ERROR); goto err; } } s->s3->empty_fragment_done = 1; } // wb->buf是和SSL綁定的一個(gè)發(fā)送buf,事先已經(jīng)malloc好了內(nèi)存,真TM慷慨! // 一個(gè)prefix_len表示在真正的record發(fā)送前緊接著的那個(gè)默默創(chuàng)建的record,調(diào)用者并不知道 // 會(huì)創(chuàng)建并發(fā)送這樣一個(gè)record p = wb->buf + prefix_len; /* write the header */ // 這段代碼還算清晰 // 但是,記住,在需要empty fragment的情況下會(huì)跑到這里兩次 *(p++)=type&0xff; wr->type=type; *(p++)=(s->version>>8); *(p++)=s->version&0xff; /* field where we are to write out packet length */ plen=p; p+=2; /* lets setup the record stuff. */ wr->data=p; wr->length=(int)len; wr->input=(unsigned char *)buf; /* we now 'read' from wr->input, wr->length bytes into * wr->data */ /* first we compress */ if (s->compress != NULL) { if (!ssl3_do_compress(s)) { SSLerr(SSL_F_DO_SSL3_WRITE,SSL_R_COMPRESSION_FAILURE); goto err; } } else { memcpy(wr->data,wr->input,wr->length); wr->input=wr->data; } /* we should still have the output to wr->data and the input * from wr->input. Length should be wr->length. * wr->data still points in the wb->buf */ if (mac_size != 0) { s->method->ssl3_enc->mac(s,&(p[wr->length]),1); wr->length+=mac_size; wr->input=p; wr->data=p; } /* ssl3_enc can only have an error on read */ s->method->ssl3_enc->enc(s,1); /* record length after mac and block padding */ s2n(wr->length,plen); /* we should now have * wr->data pointing to the encrypted data, which is * wr->length long */ wr->type=type; /* not needed but helps for debugging */ wr->length+=SSL3_RT_HEADER_LENGTH; if (create_empty_fragment) { /* we are in a recursive call; * just return the length, don't write out anything here */ // 如果是默默創(chuàng)建的那個(gè)record,則并不直接發(fā)送,目的是想將真實(shí)的record在內(nèi)存上 // 緊隨這個(gè)默默構(gòu)造好的record作為一個(gè)buffer直接發(fā)送給下層BIO。為何不分別發(fā)送兩個(gè) // record呢?我想是為了緊湊使用SSL的s3->wbuf緩沖區(qū)吧,該緩沖區(qū)事先建立,而且還 // 真不?。?6K+!唉,真不覺(jué)得實(shí)現(xiàn)者想不出更好的辦法了啊 return wr->length; } /* now let's set up wb */ wb->left = prefix_len + wr->length; wb->offset = 0; /* memorize arguments so that ssl3_write_pending can detect bad write retries later */ s->s3->wpend_tot=len; s->s3->wpend_buf=buf; s->s3->wpend_type=type; s->s3->wpend_ret=len; /* we now just need to write the buffer */ return ssl3_write_pending(s,type,buf,len); err: return -1; }上面的函數(shù)調(diào)用執(zhí)行到最后的return ssl3_write_pending(s,type,buf,len)前,就會(huì)得到下面的一共wb->left大小的緩沖區(qū):
build_record { 操作SSL的s3->wbuf。我覺(jué)得好,就繼續(xù)用 } write_raw { 往下層BIO寫(xiě)入SSL的s3->wbuf.buf的某一段 } do_ssl3_build { if (need_empty) { build_record; } build_record; ... }這樣是不是比遞歸更清晰呢?至于那個(gè)for (;;),我保留,只是修改一下ssl3_write_bytes
ssl3_write_bytes { if (left) { write_pending } do_ssl3_build for (;;) { write_raw; } }you can you up,no can no BB!我怕死無(wú)葬身之地,這個(gè)話(huà)題就此打住,who can who up!不過(guò)我要說(shuō)一點(diǎn),那就是polarssl的實(shí)現(xiàn),看看人家的ssl_write接口:
ssl_write() { if( ssl->state != SSL_HANDSHAKE_OVER ) { handshack; } if (left) { flush_pending and return <=0 } build and write record, return num }這樣調(diào)用邏輯會(huì)比較簡(jiǎn)單,更加清爽:
static int write_ssl_data( ssl_context *ssl, unsigned char *buf, size_t len ) { int ret; printf("\n%s", buf); while( len && ( ret = ssl_write( ssl, buf, len ) ) <= 0 ) { if( ret != POLARSSL_ERR_NET_WANT_READ && ret != POLARSSL_ERR_NET_WANT_WRITE ) { printf( " failed\n ! ssl_write returned %d\n\n", ret ); return -1; } } return( 0 ); }看到這樣的代碼,我想憐香惜玉的人誰(shuí)也不忍心加入if (0)逼著后來(lái)者用goto吧!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。