令人作呕的OpenSSL
生活随笔
收集整理的這篇文章主要介紹了
令人作呕的OpenSSL
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
在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ú)章法的亂。
?????? 有用主義者,或者中毒已深的人總是能給出一段代碼之所以這么寫(xiě)而不那么寫(xiě)的理由,而且理由還特別充分,以至于你也會(huì)認(rèn)為這么寫(xiě),寫(xiě)成這么爛是有理由的,當(dāng)中一定藏著什么不易理解的玄機(jī),可是,作為非神學(xué)的世俗作品,它不是圣經(jīng),不易理解本身就是一個(gè)過(guò)錯(cuò),當(dāng)然,或許是我水平太水太菜,沒(méi)有達(dá)到OpenSSL要求的那種深度,假設(shè)這樣,這篇吐嘈就是寫(xiě)給和我相同水平的菜鳥(niǎo)看的,高手請(qǐng)默默離開(kāi),不要帶走一點(diǎn)悲哀,留下的這些悲哀,讓我們這些菜鳥(niǎo)的眼淚洗刷刷吧...存在就是合理的,好吧,西西弗斯的神話表示人生就是一場(chǎng)悲哀,收成抵不上成本,它是存在的,因此是合理的,請(qǐng)不要報(bào)怨OpenSSL,它也是合理的,是的,全然正確。
?????? 開(kāi)源是偉大的,至少以前是偉大的,質(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)帶著淚去贊賞吧,鏈接在以下:
假設(shè)一個(gè)函數(shù)聲明為返回int數(shù)據(jù),可是在它的實(shí)現(xiàn)中卻:
{if ()return ret;else if ()return ret2; }這樣合理嗎?代碼當(dāng)然是正確的,可是不明朗,不光人看得不明朗,有些編譯器也會(huì)抱怨...OpenSSL中大量這樣的代碼,悲哀的是,還不是OpenSSL的全部代碼都這樣!
?????? 我知道,在使用指針的時(shí)候,推斷一下是否為NULL能夠防止SIGSEGV的發(fā)送,可是假設(shè)你能明白它不為NULL的地方,再推斷就顯得多余了,否則就會(huì)到處都是這樣的推斷了,OpenSSL中大量冗余的非NULL推斷,表明表明了什么?我將繼續(xù)苦苦思索。
?????? 我無(wú)師自通地學(xué)會(huì)了魔術(shù)字的使用,這使得我寫(xiě)的代碼帶有瞬時(shí)可理解性,當(dāng)我看了OpenSSL之后,發(fā)現(xiàn)魔術(shù)字要是用得恰到優(yōu)點(diǎn),本身就能起到加密的功能。OpenSSL定義了太多的變量以及變量的組合,以至于整個(gè)OpenSSL都是在做“什么時(shí)候?qū)⒆兞抠x給誰(shuí)”這樣的事,有用主義者以及喜歡事后論事的家伙會(huì)說(shuō),不得不這么做,OpenSSL別無(wú)選擇!或許吧,OpenSSL是別無(wú)選擇,相同實(shí)現(xiàn)SSL的其他庫(kù)卻有太多的選擇!另外我以前喜歡用int變量來(lái)控制邏輯,比方
for (...) {if () {flag = 1;}...if (flag2 == 2) {flag = 2;}... } if (flag == 3 || flag2 == 1) { ... }我以前及其痛苦地在魔術(shù)字和flags之間進(jìn)行選擇,由于我TMD根本就不懂軟件開(kāi)發(fā),我天真地以為軟件開(kāi)發(fā)就是編程,就是讓代碼跑起來(lái),直到我看到了OpenSSL,發(fā)現(xiàn)軟件開(kāi)發(fā)要做的就是讓代碼跑起來(lái)這么簡(jiǎn)單!!OpenSSL就能跑起來(lái)!前面說(shuō)了,OpenSSL定義了太多的變量,可是卻還不夠多,由于到處會(huì)出現(xiàn)if (var == 2),var2=3,var3 < 5,之類(lèi)的代碼,2,3,5代表什么意思呢?OpenSSL的凝視相同非常多,可是還不夠多,該有的凝視沒(méi)有,晦澀的地方一般都是jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
?????? 請(qǐng)注意以下代碼,它展示了C語(yǔ)言塊的本質(zhì),并不是一定要是一個(gè)完整的函數(shù),完整的條件推斷邏輯,完整的循環(huán)邏輯,我認(rèn)為這樣的教人什么是“C語(yǔ)言塊”的方式僅僅能存在于譚浩強(qiáng)的書(shū)中,但OpenSSL做得更好:
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è)塊,夾得緊緊的,舒服嗎?可能是由于作者使用了不同的C標(biāo)準(zhǔn),又想聲明新的變量,又不想動(dòng)原來(lái)的代碼,不加新塊又編譯只是,僅僅好這么玩了...但僅僅是可能而已,其實(shí)作者可能根本就沒(méi)有想這么多,我個(gè)人也喜歡這么干,有時(shí)我的想法是嘗試一個(gè)新點(diǎn)子,假設(shè)不行的話又方便恢復(fù)成原來(lái)的,又討厭使用宏,主要是打字成本太高了,其實(shí)直到不久之前,我才知道在一個(gè)塊中,變量聲明的位置并不能是隨意的,當(dāng)然,標(biāo)準(zhǔn)不同,限制也不同...
?????? C語(yǔ)言的宏是個(gè)好東西,可是也能造成流血事件,早些年的時(shí)候,我的一個(gè)經(jīng)理在開(kāi)會(huì)的時(shí)候說(shuō)要用大量的宏?duì)I造出一些不同的編譯結(jié)果,后來(lái)由于那些宏造成了可怕的宏地獄,我們每周都要加班,后來(lái)我的另外一個(gè)同事把那個(gè)領(lǐng)導(dǎo)給打了,就在辦公室,真的打出血了,我不知道是不是跟大量的宏有關(guān),我真的不知道。僅僅問(wèn)你看了以下的代碼,想打人嗎?
#define ARGV Argvint main(int Argc, char *ARGV[])這么做的藝術(shù)性何在?我將繼續(xù)苦逼地上下而求索。
?????? OpenSSL代碼中大量的#if 0不說(shuō),還有以下奇葩的,凝視都給宏定義屏蔽了,編譯器迷惑了,凝視的第一行看得出是個(gè)凝視,可是卻找不到*/,哦,原來(lái)如此,凝視的后面部分被#if 0這個(gè)宏屏蔽了...
#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; #elses->state = s->server ? SSL_ST_ACCEPT : SSL_ST_CONNECT; #endif注意上面凝視第一行的那個(gè)“(and”,看得出是作者有益這么做的,以表現(xiàn)一下自己的立體主義??
?????? OpenSSL毫無(wú)一致的風(fēng)格,無(wú)論是縮進(jìn)還是代碼本身,甚至在一個(gè)函數(shù)中都沒(méi)有一致的風(fēng)格,相似以下這樣:
int func(){...a=b;c = d; }假設(shè)我寫(xiě)出這樣的代碼,又要被罵了,可是慢慢的,我不認(rèn)為因此被罵是一種讓人痛苦的事,就像OpenSSL一樣將自虐當(dāng)成了快感的來(lái)源!
?????? 若不是我把以下的這段代碼的業(yè)務(wù)部分摳去,它絕對(duì)能夠參加IOCCC了,其實(shí)摳業(yè)務(wù)代碼的過(guò)程是痛苦的,全然沒(méi)有庖丁解牛那樣的快感,相反,就像摳屁股眼子一樣痛苦...
some_function(...) {...if (s->session->sess_cert != NULL){ #ifndef OPENSSL_NO_RSAif (s->session->sess_cert->peer_rsa_tmp != NULL){...} #endif...}else{...;}... #ifndef OPENSSL_NO_RSAif (alg & SSL_kRSA){...} #else /* OPENSSL_NO_RSA */if (0); #endif #ifndef OPENSSL_NO_DHelse if (alg & SSL_kEDH){... #ifndef OPENSSL_NO_RSAif (alg & SSL_aRSA)... #elseif (0); #endif #ifndef OPENSSL_NO_DSAelse 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_ECDHelse if (alg & SSL_kECDHE){...if (0) ; #ifndef OPENSSL_NO_RSAelse if (alg & SSL_aRSA)...; #endif #ifndef OPENSSL_NO_ECDSAelse 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_RSAif (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_DSAif (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_ECDSAif (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_RSAif (rsa != NULL)RSA_free(rsa); #endif #ifndef OPENSSL_NO_DHif (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)系,讓相互排斥代碼的某部分不運(yùn)行??唉,宏與宏之間發(fā)生了關(guān)系,你就不再是C編程,而是宏編程...話說(shuō),上述的代碼實(shí)際上是一個(gè)不含業(yè)務(wù)的邏輯框架,就像鋼混框架結(jié)構(gòu)建筑的那個(gè)大架子一樣,和IOCCC獲獎(jiǎng)代碼還是天上地下的,真正的IOCCC代碼是無(wú)框架的,框架隱藏于的業(yè)務(wù)本身,它的美感相似于相似海洋軟體動(dòng)物的那種美。
?????? 事實(shí)證明,C語(yǔ)言的代碼跳轉(zhuǎn)機(jī)制是多種多樣的,僅僅會(huì)用goto那叫井底之蛙,可是有些時(shí)候,某個(gè)代碼段僅僅能用goto達(dá)到,這不是逼著人用goto的嗎?請(qǐ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)暴掉,只是還有一種方式,把0的意義改掉。大量的#if 0,#if 1,if (0), if (1)的存在,外加一些令人看到“世界在進(jìn)步”的凝視,將OpenSSL變成了一座僵尸博物館,這些永遠(yuǎn)都不會(huì)被運(yùn)行到的代碼旁邊都會(huì)有一些個(gè)凝視,詮釋著它們以前的光輝和日前為何變成了木乃伊??墒菫楹尾话阉鼈冎苯觿h掉呢?既然已經(jīng)知道了它們已然無(wú)用而且知道了為什么已然無(wú)用,還留著它們,我想作者們都是些懷舊之士吧。這使我們這些后來(lái)人在讀代碼或者改代碼的時(shí)候不得不先預(yù)處理一遍。對(duì)于我個(gè)人來(lái)講,我不喜歡預(yù)處理,我直接手工刪掉那些永不被運(yùn)行的代碼,我甚至將此事作為當(dāng)成一種無(wú)聊時(shí)的消遣,和展Windows注冊(cè)表一展一下午一樣獲得一種升華意義的快感!我真的以前展過(guò)注冊(cè)表,展了一下午都沒(méi)有展完...
?????? 3年前,我以前在OpenSSL的一個(gè)engine里面大量使用以下的代碼:
do { ... if (...)break; ... }while(0);我因這樣的代碼而被罵狗屎,只是當(dāng)時(shí)我并沒(méi)有生氣,反而和還有一個(gè)同事在旁邊偷笑,聽(tīng)說(shuō),笑能長(zhǎng)壽,看來(lái)以后要多看看OpenSSL的代碼了。
?????? 笑固然好,可是哭是還有一種釋放壓力的手段,有時(shí)會(huì)比笑的效果更好。可是僅僅是上面這些還是無(wú)法把我弄哭的,能把我弄哭的是一段代碼的實(shí)現(xiàn)邏輯,事情是這樣的...老子雖不是什么高人,起碼也作為碼農(nóng)辛勤耕耘好記載了,被OpenSSL如此蹂躪真的是說(shuō)不出來(lái)的苦啊!
?????? SSL數(shù)據(jù)是留式的,即它沒(méi)有邊界,不像數(shù)據(jù)報(bào)協(xié)議,在底層,SSL紀(jì)錄協(xié)議是封裝在一個(gè)recode塊里面的,能夠認(rèn)為在底層SSL是有邊界的,可是在上層它和TCP一樣,沒(méi)有邊界??墒俏移盟鼇?lái)傳輸有邊界的IP數(shù)據(jù)報(bào),OpenSSL的SSL_write/SSL_read接口又沒(méi)有暴露出SSL record的概念,我是多么希望SSL_write每次將傳入的buff作為一個(gè)record發(fā)送,而SSL_read則每次僅將一個(gè)record數(shù)據(jù)返回調(diào)用者啊,然而沒(méi)有不論什么標(biāo)準(zhǔn)規(guī)定它應(yīng)該這么做,因此我就不能奢望OpenSSL是如此實(shí)現(xiàn)的。
?????? 幸好OpenSSL它是開(kāi)源的,代碼能夠自己看,RTFSC!正如Linus大神說(shuō)的那樣。可是看看ssl3_write_bytes的實(shí)現(xiàn):
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;elsenw=n;// 我認(rèn)為這是個(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ì)認(rèn)為,一個(gè)buff可能會(huì)分為多次發(fā)送,所以有了一個(gè)for(;;),直到發(fā)送完為止,假設(shè)接口行為定義良好,我應(yīng)該放棄希望了,由于依照以上它的實(shí)現(xiàn)邏輯,一個(gè)buff可能會(huì)被切割為多段,每段調(diào)用do_ssl3_write發(fā)送,這樣一個(gè)buff就會(huì)形成多個(gè)record,從而打破了我的幻想,此時(shí)我想哭,由于我不得不再次去操家伙攪狗屎,噢,多么痛的領(lǐng)悟,多么直白的坦言。
?????? 幸好有高人相助,告訴我,理論上應(yīng)該是一次write構(gòu)造一個(gè)record的,我對(duì)此人的神乎膜拜促使我深入了do_ssl3_write函數(shù)內(nèi)部,然后我打個(gè)個(gè)噴嚏,一眨巴淚眼,鼻涕吸到了嗓子里,咸咸的,但不苦...
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è)運(yùn)行流跳轉(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;elsemac_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è)recordp = 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*/// 假設(shè)是默默創(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ū)事先建立,而且還// 真不小:16K+!唉,真不認(rèn)為實(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)用運(yùn)行到最后的return ssl3_write_pending(s,type,buf,len)前,就會(huì)得到以下的一共wb->left大小的緩沖區(qū):
|empty record header|empty record data|real record header|real record data|
終于的buff構(gòu)造好了,能夠發(fā)送了吧,好的,能夠發(fā)送了!可是底層機(jī)制又來(lái)找茬了...在非堵塞IO模式下,底層的BIO并不一定能保證發(fā)完wb->left這么多數(shù)據(jù),那么發(fā)多少返回多少,這也正常,關(guān)鍵是返回到了ssl3_write_bytes函數(shù),也就是那個(gè)for(;;)調(diào)用do_ssl3_write的函數(shù),然后一大堆if推斷,要么繼續(xù),要么直接終于返回給SSL_write,無(wú)論如何,在你下次調(diào)用ssl3_write_bytes里面的do_ssl3_write的時(shí)候,僅僅要這兩個(gè)個(gè)record沒(méi)有寫(xiě)完,即SSL的s3->wbuf.left不為0,就會(huì)在do_ssl3_write的最開(kāi)始處直接調(diào)用ssl3_write_pending來(lái)保證一個(gè)record的寫(xiě)入完成。
?????? 全部的問(wèn)題在于,do_ssl3_write太復(fù)雜了,做的事情太多了,它做了3件事:1.構(gòu)造empty fragment;2.構(gòu)造真實(shí)record;3.保證這兩個(gè)record發(fā)送完成。邏輯太復(fù)雜,因此才邀請(qǐng)各種跳轉(zhuǎn)上陣...在給出我認(rèn)為合理的邏輯之前,先簡(jiǎn)單說(shuō)下什么是empty fragment。它實(shí)際上是一個(gè)缺陷的修復(fù),即針對(duì)CBC IV的攻擊,empty frag機(jī)制在每次發(fā)送record前先發(fā)送一個(gè)empty frag record,內(nèi)部一些沒(méi)用的數(shù)據(jù),接收端能夠在SSL協(xié)議層解密后隨意處理,它的目的就是在數(shù)據(jù)中間插入一些隨機(jī)因素以加大CBC模式的IV推測(cè)的難度。
?????? 我想不明白,發(fā)送上次未完成的數(shù)據(jù)為何要放在這么深的位置,我也想不明白,為何要用遞歸...難道就不能封裝一個(gè)build_record的函數(shù)嗎?難道就不能封裝一個(gè)write_raw函數(shù)嗎?既然empty fragment是一個(gè)安全加固機(jī)制,為何要隱藏它呢?直接:
build_record {操作SSL的s3->wbuf。我認(rèn)為好,就繼續(xù)用 } write_raw {往下層BIO寫(xiě)入SSL的s3->wbuf.buf的某一段 } do_ssl3_build {if (need_empty) {build_record;} build_record; ... }這樣是不是比遞歸更清晰呢?至于那個(gè)for (;;),我保留,僅僅是改動(dòng)一下ssl3_write_bytes
ssl3_write_bytes {if (left) {write_pending}do_ssl3_buildfor (;;) {write_raw;} }you can you up,no can no BB!我怕死無(wú)葬身之地,這個(gè)話題就此打住,who can who up!只是我要說(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吧!
?????? 我對(duì)ssl3_write_pending的理解真的非常對(duì)嗎?不,我錯(cuò)了!ssl3_write_pending真的會(huì)在沒(méi)有寫(xiě)完record數(shù)據(jù)的情況下將left清0,那就是在DTLS的情況下,此時(shí)其調(diào)用者的那句凝視就說(shuō)對(duì)了:
/* next chunk of data should get another prepended empty fragment
??? ??? ??? ?* in ciphersuites with known-IV weakness: */
這就是OpenSSL的全部,連凝視說(shuō)的都不是全部情況。當(dāng)然OpenSSL并沒(méi)有明白地凝視,總是保留一點(diǎn)解釋的空間,所以不要看它的凝視,還是看代碼吧,假設(shè)你想自虐的話...
?????? 首先聲明的一點(diǎn)是,我并沒(méi)有惡意詆毀的意思,也并沒(méi)有針對(duì)什么,比起生活中的大喜大悲,比起工作中的大起大落,比起追求理想過(guò)程中的遭遇坎坷,OpenSSL的折磨其實(shí)是一種幸福,僅僅是對(duì)幸福的解讀,有時(shí)能夠認(rèn)為是,痛并快樂(lè)著,齊秦如是說(shuō)...
?????? OpenSSL代碼真的非常爛,太爛,毫無(wú)章法的亂。
?????? 有用主義者,或者中毒已深的人總是能給出一段代碼之所以這么寫(xiě)而不那么寫(xiě)的理由,而且理由還特別充分,以至于你也會(huì)認(rèn)為這么寫(xiě),寫(xiě)成這么爛是有理由的,當(dāng)中一定藏著什么不易理解的玄機(jī),可是,作為非神學(xué)的世俗作品,它不是圣經(jīng),不易理解本身就是一個(gè)過(guò)錯(cuò),當(dāng)然,或許是我水平太水太菜,沒(méi)有達(dá)到OpenSSL要求的那種深度,假設(shè)這樣,這篇吐嘈就是寫(xiě)給和我相同水平的菜鳥(niǎo)看的,高手請(qǐng)默默離開(kāi),不要帶走一點(diǎn)悲哀,留下的這些悲哀,讓我們這些菜鳥(niǎo)的眼淚洗刷刷吧...存在就是合理的,好吧,西西弗斯的神話表示人生就是一場(chǎng)悲哀,收成抵不上成本,它是存在的,因此是合理的,請(qǐng)不要報(bào)怨OpenSSL,它也是合理的,是的,全然正確。
?????? 開(kāi)源是偉大的,至少以前是偉大的,質(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
?????? 相同值得贊賞的是,OpenVPN的代碼,相同狠爛!贊賞鏈接之前,請(qǐng)讓我拋塊磚,來(lái)點(diǎn)小菜。我們開(kāi)始吧!假設(shè)一個(gè)函數(shù)聲明為返回int數(shù)據(jù),可是在它的實(shí)現(xiàn)中卻:
{if ()return ret;else if ()return ret2; }這樣合理嗎?代碼當(dāng)然是正確的,可是不明朗,不光人看得不明朗,有些編譯器也會(huì)抱怨...OpenSSL中大量這樣的代碼,悲哀的是,還不是OpenSSL的全部代碼都這樣!
?????? 我知道,在使用指針的時(shí)候,推斷一下是否為NULL能夠防止SIGSEGV的發(fā)送,可是假設(shè)你能明白它不為NULL的地方,再推斷就顯得多余了,否則就會(huì)到處都是這樣的推斷了,OpenSSL中大量冗余的非NULL推斷,表明表明了什么?我將繼續(xù)苦苦思索。
?????? 我無(wú)師自通地學(xué)會(huì)了魔術(shù)字的使用,這使得我寫(xiě)的代碼帶有瞬時(shí)可理解性,當(dāng)我看了OpenSSL之后,發(fā)現(xiàn)魔術(shù)字要是用得恰到優(yōu)點(diǎn),本身就能起到加密的功能。OpenSSL定義了太多的變量以及變量的組合,以至于整個(gè)OpenSSL都是在做“什么時(shí)候?qū)⒆兞抠x給誰(shuí)”這樣的事,有用主義者以及喜歡事后論事的家伙會(huì)說(shuō),不得不這么做,OpenSSL別無(wú)選擇!或許吧,OpenSSL是別無(wú)選擇,相同實(shí)現(xiàn)SSL的其他庫(kù)卻有太多的選擇!另外我以前喜歡用int變量來(lái)控制邏輯,比方
for (...) {if () {flag = 1;}...if (flag2 == 2) {flag = 2;}... } if (flag == 3 || flag2 == 1) { ... }我以前及其痛苦地在魔術(shù)字和flags之間進(jìn)行選擇,由于我TMD根本就不懂軟件開(kāi)發(fā),我天真地以為軟件開(kāi)發(fā)就是編程,就是讓代碼跑起來(lái),直到我看到了OpenSSL,發(fā)現(xiàn)軟件開(kāi)發(fā)要做的就是讓代碼跑起來(lái)這么簡(jiǎn)單!!OpenSSL就能跑起來(lái)!前面說(shuō)了,OpenSSL定義了太多的變量,可是卻還不夠多,由于到處會(huì)出現(xiàn)if (var == 2),var2=3,var3 < 5,之類(lèi)的代碼,2,3,5代表什么意思呢?OpenSSL的凝視相同非常多,可是還不夠多,該有的凝視沒(méi)有,晦澀的地方一般都是jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
?????? 請(qǐng)注意以下代碼,它展示了C語(yǔ)言塊的本質(zhì),并不是一定要是一個(gè)完整的函數(shù),完整的條件推斷邏輯,完整的循環(huán)邏輯,我認(rèn)為這樣的教人什么是“C語(yǔ)言塊”的方式僅僅能存在于譚浩強(qiáng)的書(shū)中,但OpenSSL做得更好:
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è)塊,夾得緊緊的,舒服嗎?可能是由于作者使用了不同的C標(biāo)準(zhǔn),又想聲明新的變量,又不想動(dòng)原來(lái)的代碼,不加新塊又編譯只是,僅僅好這么玩了...但僅僅是可能而已,其實(shí)作者可能根本就沒(méi)有想這么多,我個(gè)人也喜歡這么干,有時(shí)我的想法是嘗試一個(gè)新點(diǎn)子,假設(shè)不行的話又方便恢復(fù)成原來(lái)的,又討厭使用宏,主要是打字成本太高了,其實(shí)直到不久之前,我才知道在一個(gè)塊中,變量聲明的位置并不能是隨意的,當(dāng)然,標(biāo)準(zhǔn)不同,限制也不同...
?????? C語(yǔ)言的宏是個(gè)好東西,可是也能造成流血事件,早些年的時(shí)候,我的一個(gè)經(jīng)理在開(kāi)會(huì)的時(shí)候說(shuō)要用大量的宏?duì)I造出一些不同的編譯結(jié)果,后來(lái)由于那些宏造成了可怕的宏地獄,我們每周都要加班,后來(lái)我的另外一個(gè)同事把那個(gè)領(lǐng)導(dǎo)給打了,就在辦公室,真的打出血了,我不知道是不是跟大量的宏有關(guān),我真的不知道。僅僅問(wèn)你看了以下的代碼,想打人嗎?
#define ARGV Argvint main(int Argc, char *ARGV[])這么做的藝術(shù)性何在?我將繼續(xù)苦逼地上下而求索。
?????? OpenSSL代碼中大量的#if 0不說(shuō),還有以下奇葩的,凝視都給宏定義屏蔽了,編譯器迷惑了,凝視的第一行看得出是個(gè)凝視,可是卻找不到*/,哦,原來(lái)如此,凝視的后面部分被#if 0這個(gè)宏屏蔽了...
#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; #elses->state = s->server ? SSL_ST_ACCEPT : SSL_ST_CONNECT; #endif注意上面凝視第一行的那個(gè)“(and”,看得出是作者有益這么做的,以表現(xiàn)一下自己的立體主義??
?????? OpenSSL毫無(wú)一致的風(fēng)格,無(wú)論是縮進(jìn)還是代碼本身,甚至在一個(gè)函數(shù)中都沒(méi)有一致的風(fēng)格,相似以下這樣:
int func(){...a=b;c = d; }假設(shè)我寫(xiě)出這樣的代碼,又要被罵了,可是慢慢的,我不認(rèn)為因此被罵是一種讓人痛苦的事,就像OpenSSL一樣將自虐當(dāng)成了快感的來(lái)源!
?????? 若不是我把以下的這段代碼的業(yè)務(wù)部分摳去,它絕對(duì)能夠參加IOCCC了,其實(shí)摳業(yè)務(wù)代碼的過(guò)程是痛苦的,全然沒(méi)有庖丁解牛那樣的快感,相反,就像摳屁股眼子一樣痛苦...
some_function(...) {...if (s->session->sess_cert != NULL){ #ifndef OPENSSL_NO_RSAif (s->session->sess_cert->peer_rsa_tmp != NULL){...} #endif...}else{...;}... #ifndef OPENSSL_NO_RSAif (alg & SSL_kRSA){...} #else /* OPENSSL_NO_RSA */if (0); #endif #ifndef OPENSSL_NO_DHelse if (alg & SSL_kEDH){... #ifndef OPENSSL_NO_RSAif (alg & SSL_aRSA)... #elseif (0); #endif #ifndef OPENSSL_NO_DSAelse 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_ECDHelse if (alg & SSL_kECDHE){...if (0) ; #ifndef OPENSSL_NO_RSAelse if (alg & SSL_aRSA)...; #endif #ifndef OPENSSL_NO_ECDSAelse 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_RSAif (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_DSAif (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_ECDSAif (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_RSAif (rsa != NULL)RSA_free(rsa); #endif #ifndef OPENSSL_NO_DHif (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)系,讓相互排斥代碼的某部分不運(yùn)行??唉,宏與宏之間發(fā)生了關(guān)系,你就不再是C編程,而是宏編程...話說(shuō),上述的代碼實(shí)際上是一個(gè)不含業(yè)務(wù)的邏輯框架,就像鋼混框架結(jié)構(gòu)建筑的那個(gè)大架子一樣,和IOCCC獲獎(jiǎng)代碼還是天上地下的,真正的IOCCC代碼是無(wú)框架的,框架隱藏于的業(yè)務(wù)本身,它的美感相似于相似海洋軟體動(dòng)物的那種美。
?????? 事實(shí)證明,C語(yǔ)言的代碼跳轉(zhuǎn)機(jī)制是多種多樣的,僅僅會(huì)用goto那叫井底之蛙,可是有些時(shí)候,某個(gè)代碼段僅僅能用goto達(dá)到,這不是逼著人用goto的嗎?請(qǐ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)暴掉,只是還有一種方式,把0的意義改掉。大量的#if 0,#if 1,if (0), if (1)的存在,外加一些令人看到“世界在進(jìn)步”的凝視,將OpenSSL變成了一座僵尸博物館,這些永遠(yuǎn)都不會(huì)被運(yùn)行到的代碼旁邊都會(huì)有一些個(gè)凝視,詮釋著它們以前的光輝和日前為何變成了木乃伊??墒菫楹尾话阉鼈冎苯觿h掉呢?既然已經(jīng)知道了它們已然無(wú)用而且知道了為什么已然無(wú)用,還留著它們,我想作者們都是些懷舊之士吧。這使我們這些后來(lái)人在讀代碼或者改代碼的時(shí)候不得不先預(yù)處理一遍。對(duì)于我個(gè)人來(lái)講,我不喜歡預(yù)處理,我直接手工刪掉那些永不被運(yùn)行的代碼,我甚至將此事作為當(dāng)成一種無(wú)聊時(shí)的消遣,和展Windows注冊(cè)表一展一下午一樣獲得一種升華意義的快感!我真的以前展過(guò)注冊(cè)表,展了一下午都沒(méi)有展完...
?????? 3年前,我以前在OpenSSL的一個(gè)engine里面大量使用以下的代碼:
do { ... if (...)break; ... }while(0);我因這樣的代碼而被罵狗屎,只是當(dāng)時(shí)我并沒(méi)有生氣,反而和還有一個(gè)同事在旁邊偷笑,聽(tīng)說(shuō),笑能長(zhǎng)壽,看來(lái)以后要多看看OpenSSL的代碼了。
?????? 笑固然好,可是哭是還有一種釋放壓力的手段,有時(shí)會(huì)比笑的效果更好。可是僅僅是上面這些還是無(wú)法把我弄哭的,能把我弄哭的是一段代碼的實(shí)現(xiàn)邏輯,事情是這樣的...老子雖不是什么高人,起碼也作為碼農(nóng)辛勤耕耘好記載了,被OpenSSL如此蹂躪真的是說(shuō)不出來(lái)的苦啊!
?????? SSL數(shù)據(jù)是留式的,即它沒(méi)有邊界,不像數(shù)據(jù)報(bào)協(xié)議,在底層,SSL紀(jì)錄協(xié)議是封裝在一個(gè)recode塊里面的,能夠認(rèn)為在底層SSL是有邊界的,可是在上層它和TCP一樣,沒(méi)有邊界??墒俏移盟鼇?lái)傳輸有邊界的IP數(shù)據(jù)報(bào),OpenSSL的SSL_write/SSL_read接口又沒(méi)有暴露出SSL record的概念,我是多么希望SSL_write每次將傳入的buff作為一個(gè)record發(fā)送,而SSL_read則每次僅將一個(gè)record數(shù)據(jù)返回調(diào)用者啊,然而沒(méi)有不論什么標(biāo)準(zhǔn)規(guī)定它應(yīng)該這么做,因此我就不能奢望OpenSSL是如此實(shí)現(xiàn)的。
?????? 幸好OpenSSL它是開(kāi)源的,代碼能夠自己看,RTFSC!正如Linus大神說(shuō)的那樣。可是看看ssl3_write_bytes的實(shí)現(xiàn):
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;elsenw=n;// 我認(rèn)為這是個(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ì)認(rèn)為,一個(gè)buff可能會(huì)分為多次發(fā)送,所以有了一個(gè)for(;;),直到發(fā)送完為止,假設(shè)接口行為定義良好,我應(yīng)該放棄希望了,由于依照以上它的實(shí)現(xiàn)邏輯,一個(gè)buff可能會(huì)被切割為多段,每段調(diào)用do_ssl3_write發(fā)送,這樣一個(gè)buff就會(huì)形成多個(gè)record,從而打破了我的幻想,此時(shí)我想哭,由于我不得不再次去操家伙攪狗屎,噢,多么痛的領(lǐng)悟,多么直白的坦言。
?????? 幸好有高人相助,告訴我,理論上應(yīng)該是一次write構(gòu)造一個(gè)record的,我對(duì)此人的神乎膜拜促使我深入了do_ssl3_write函數(shù)內(nèi)部,然后我打個(gè)個(gè)噴嚏,一眨巴淚眼,鼻涕吸到了嗓子里,咸咸的,但不苦...
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è)運(yùn)行流跳轉(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;elsemac_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è)recordp = 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*/// 假設(shè)是默默創(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ū)事先建立,而且還// 真不小:16K+!唉,真不認(rèn)為實(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)用運(yùn)行到最后的return ssl3_write_pending(s,type,buf,len)前,就會(huì)得到以下的一共wb->left大小的緩沖區(qū):
|empty record header|empty record data|real record header|real record data|
終于的buff構(gòu)造好了,能夠發(fā)送了吧,好的,能夠發(fā)送了!可是底層機(jī)制又來(lái)找茬了...在非堵塞IO模式下,底層的BIO并不一定能保證發(fā)完wb->left這么多數(shù)據(jù),那么發(fā)多少返回多少,這也正常,關(guān)鍵是返回到了ssl3_write_bytes函數(shù),也就是那個(gè)for(;;)調(diào)用do_ssl3_write的函數(shù),然后一大堆if推斷,要么繼續(xù),要么直接終于返回給SSL_write,無(wú)論如何,在你下次調(diào)用ssl3_write_bytes里面的do_ssl3_write的時(shí)候,僅僅要這兩個(gè)個(gè)record沒(méi)有寫(xiě)完,即SSL的s3->wbuf.left不為0,就會(huì)在do_ssl3_write的最開(kāi)始處直接調(diào)用ssl3_write_pending來(lái)保證一個(gè)record的寫(xiě)入完成。
?????? 全部的問(wèn)題在于,do_ssl3_write太復(fù)雜了,做的事情太多了,它做了3件事:1.構(gòu)造empty fragment;2.構(gòu)造真實(shí)record;3.保證這兩個(gè)record發(fā)送完成。邏輯太復(fù)雜,因此才邀請(qǐng)各種跳轉(zhuǎn)上陣...在給出我認(rèn)為合理的邏輯之前,先簡(jiǎn)單說(shuō)下什么是empty fragment。它實(shí)際上是一個(gè)缺陷的修復(fù),即針對(duì)CBC IV的攻擊,empty frag機(jī)制在每次發(fā)送record前先發(fā)送一個(gè)empty frag record,內(nèi)部一些沒(méi)用的數(shù)據(jù),接收端能夠在SSL協(xié)議層解密后隨意處理,它的目的就是在數(shù)據(jù)中間插入一些隨機(jī)因素以加大CBC模式的IV推測(cè)的難度。
?????? 我想不明白,發(fā)送上次未完成的數(shù)據(jù)為何要放在這么深的位置,我也想不明白,為何要用遞歸...難道就不能封裝一個(gè)build_record的函數(shù)嗎?難道就不能封裝一個(gè)write_raw函數(shù)嗎?既然empty fragment是一個(gè)安全加固機(jī)制,為何要隱藏它呢?直接:
build_record {操作SSL的s3->wbuf。我認(rèn)為好,就繼續(xù)用 } write_raw {往下層BIO寫(xiě)入SSL的s3->wbuf.buf的某一段 } do_ssl3_build {if (need_empty) {build_record;} build_record; ... }這樣是不是比遞歸更清晰呢?至于那個(gè)for (;;),我保留,僅僅是改動(dòng)一下ssl3_write_bytes
ssl3_write_bytes {if (left) {write_pending}do_ssl3_buildfor (;;) {write_raw;} }you can you up,no can no BB!我怕死無(wú)葬身之地,這個(gè)話題就此打住,who can who up!只是我要說(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吧!
?????? 我對(duì)ssl3_write_pending的理解真的非常對(duì)嗎?不,我錯(cuò)了!ssl3_write_pending真的會(huì)在沒(méi)有寫(xiě)完record數(shù)據(jù)的情況下將left清0,那就是在DTLS的情況下,此時(shí)其調(diào)用者的那句凝視就說(shuō)對(duì)了:
/* next chunk of data should get another prepended empty fragment
??? ??? ??? ?* in ciphersuites with known-IV weakness: */
這就是OpenSSL的全部,連凝視說(shuō)的都不是全部情況。當(dāng)然OpenSSL并沒(méi)有明白地凝視,總是保留一點(diǎn)解釋的空間,所以不要看它的凝視,還是看代碼吧,假設(shè)你想自虐的話...
轉(zhuǎn)載于:https://www.cnblogs.com/bhlsheji/p/4306183.html
總結(jié)
以上是生活随笔為你收集整理的令人作呕的OpenSSL的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: BE病毒8.8E+1&nbsp;
- 下一篇: [Flex] 组件Tree系列 —— 阻