十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
在OpenSSL心脏出血之后,我相信很多人都出了血,并且流了泪...网上瞬间出现了大量吐嘈OpenSSL的文章或段子,仿佛内心的窝火一瞬间被释放了出来,跟着这场疯闹,我也吐一下嘈,以雪这些年被OpenSSL蹂躏之辱,也许可以顺便展现一下我的无知与愚昧,但只是也许...
首先声明的一点是,我并没有恶意诋毁的意思,也并没有针对什么,比起生活中的大喜大悲,比起工作中的大起大落,比起追求理想过程中的遭遇坎坷,OpenSSL的折磨其实是一种幸福,只是对幸福的解读,有时可以认为是,痛并快乐着,齐秦如是说...
OpenSSL代码真的很烂,太烂,毫无章法的乱。
实用主义者,或者中毒已深的人总是能给出一段代码之所以这么写而不那么写的理由,并且理由还特别充分,以至于你也会认为这么写,写成这么烂是有理由的,其中一定藏着什么不易理解的玄机,但是,作为非神学的世俗作品,它不是圣经,不易理解本身就是一个过错,当然,也许是我水平太水太菜,没有达到OpenSSL要求的那种深度,如果这样,这篇吐嘈就是写给和我相同水平的菜鸟看的,高手请默默离开,不要带走一点悲哀,留下的这些悲哀,让我们这些菜鸟的眼泪洗刷刷吧...存在就是合理的,好吧,西西弗斯的神话表示人生就是一场悲哀,收成抵不上成本,它是存在的,因此是合理的,请不要报怨OpenSSL,它也是合理的,是的,完全正确。
开源是伟大的,至少曾经是伟大的,质疑它的人,一定没有体验过Linus Torvalds爆米且口的那份激动与听众受虐般的激情,也不一定拥有站在Richard Stallman或者极端的Eric S. Raymond脚下的那份敬畏和感动。但是OpenSSL出现以后,表明开源所表达的自由还有另外一层意思,那就是代码拥有不受审查的自由,有烂的自由,更多的,每个人都有使用烂代码的自由,更进一步的,每个人都有把烂代码说成艺术的自由,而这份自由,被OpenSSL那黑翼般的力量煽动,带给了每一个人,于是,心脏流血的时候,我攥起了拳头...
说多了都是泪...突然看到了一个项目,OpenBSD发起一个清理OpenSSL代码的项目,就想继续泪下去,等看完我这篇吐嘈,请带着泪去欣赏吧,链接在下面:
成都创新互联是一家专注于网站建设、成都网站制作与策划设计,永川网站建设哪家好?成都创新互联做网站,专注于网站建设十年,网设计领域的专业建站公司;建站业务涵盖:永川等地区。永川做网站价格咨询:13518219792
清爽链接1
清爽链接2
同样值得欣赏的是,Open×××的代码,同样狠烂!欣赏链接之前,请让我抛块砖,来点小菜。我们开始吧!{ if () return ret; else if () return ret2; }这样合理吗?代码当然是正确的,但是不明朗,不光人看得不明朗,有些编译器也会抱怨...OpenSSL中大量这种代码,悲哀的是,还不是OpenSSL的全部代码都这样!
for (...) { if () { flag = 1; } ... if (flag2 == 2) { flag = 2; } ... } if (flag == 3 || flag2 == 1) { ... }我曾经及其痛苦地在魔术字和flags之间进行选择,因为我TMD根本就不懂软件开发,我天真地以为软件开发就是编程,就是让代码跑起来,直到我看到了OpenSSL,发现软件开发要做的就是让代码跑起来这么简单!!OpenSSL就能跑起来!前面说了,OpenSSL定义了太多的变量,但是却还不够多,因为到处会出现if (var == 2),var2=3,var3 < 5,之类的代码,2,3,5代表什么意思呢?OpenSSL的注释同样很多,但是还不够多,该有的注释没有,晦涩的地方一般都是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; } ... }在函数中间夹了一个块,夹得紧紧的,舒服吗?可能是因为作者使用了不同的C标准,又想声明新的变量,又不想动原来的代码,不加新块又编译不过,只好这么玩了...但只是可能而已,事实上作者可能根本就没有想这么多,我个人也喜欢这么干,有时我的想法是尝试一个新点子,如果不行的话又方便恢复成原来的,又讨厌使用宏,主要是打字成本太高了,事实上直到不久之前,我才知道在一个块中,变量声明的位置并不能是随意的,当然,标准不同,限制也不同...
#define ARGV Argv int main(int Argc, char *ARGV[])这么做的艺术性何在?我将继续苦逼地上下而求索。
#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注意上面注释第一行的那个“(and”,看得出是作者故意这么做的,以表现一下自己的立体主义??
int func() { ... a=b; c = d; }如果我写出这种代码,又要被骂了,但是慢慢的,我不觉得因此被骂是一种让人痛苦的事,就像OpenSSL一样将自虐当成了快感的来源!
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); }代码是有点长了,但是实际的代码就是如此!简直就是宏的地狱,if (0)这种代码的目的就是为了胶合诸多宏之间的互斥关系,让互斥代码的某部分不执行??唉,宏与宏之间发生了关系,你就不再是C编程,而是宏编程...话说,上述的代码实际上是一个不含业务的逻辑框架,就像钢混框架结构建筑的那个大架子一样,和IOCCC获奖代码还是天上地下的,真正的IOCCC代码是无框架的,框架隐藏于的业务本身,它的美感类似于类似海洋软体动物的那种美。
if(!ok) goto end; if (0) { end: X509_get_pubkey_parameters(NULL,ctx->chain); }事实上想玩好if (0)只有两种方法,第一就是使用宏把if (0)屏蔽掉,第二就是使用goto把if (0)强暴掉,不过还有一种方式,把0的意义改掉。大量的#if 0,#if 1,if (0), if (1)的存在,外加一些令人看到“世界在进步”的注释,将OpenSSL变成了一座僵尸博物馆,这些永远都不会被执行到的代码旁边都会有一些个注释,诠释着它们曾经的光辉和日前为何变成了木乃伊。可是为何不把它们直接删掉呢?既然已经知道了它们已然无用并且知道了为什么已然无用,还留着它们,我想作者们都是些怀旧之士吧。这使我们这些后来人在读代码或者改代码的时候不得不先预处理一遍。对于我个人来讲,我不喜欢预处理,我直接手工删掉那些永不被执行的代码,我甚至将此事作为当成一种无聊时的消遣,和展Windows注册表一展一下午一样获得一种升华意义的快感!我真的曾经展过注册表,展了一下午都没有展完...
do { ... if (...) break; ... }while(0);我因这种代码而被骂狗屎,不过当时我并没有生气,反而和另一个同事在旁边偷笑,听说,笑能长寿,看来以后要多看看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; // 我觉得这是个核心函数 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; } }看到这段代码,一般人会怎么想?当然深深中了OpenSSL邪毒的那帮人不属于一般人。一般人看了会觉得,一个buff可能会分为多次发送,所以有了一个for(;;),直到发送完为止,如果接口行为定义良好,我应该放弃希望了,因为按照以上它的实现逻辑,一个buff可能会被分割为多段,每段调用do_ssl3_write发送,这样一个buff就会形成多个record,从而打破了我的幻想,此时我想哭,因为我不得不再次去操家伙搅狗屎,噢,多么痛的领悟,多么直白的坦言。
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) // 在一开始的位置,处理逻辑就被劫持了,因此我就必须注意left在什么情况下不为0 // 这个执行流跳转得很诡异!太诡异! 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,在上层调用的时候,这个参数为0,这就意味着 // 肯定有什么地方以1为参数调用了本函数。这个empty fragment我后面会解释。 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) */ // 递归调用?我kao,这个函数竟然有两段逻辑: // 1.默默创建一个新的record; // 2.创建封装buf的record并和递归调用中默默创建的那个record一起发送 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绑定的一个发送buf,事先已经malloc好了内存,真TM慷慨! // 一个prefix_len表示在真正的record发送前紧接着的那个默默创建的record,调用者并不知道 // 会创建并发送这样一个record p = wb->buf + prefix_len; /* write the header */ // 这段代码还算清晰 // 但是,记住,在需要empty fragment的情况下会跑到这里两次 *(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 */ // 如果是默默创建的那个record,则并不直接发送,目的是想将真实的record在内存上 // 紧随这个默默构造好的record作为一个buffer直接发送给下层BIO。为何不分别发送两个 // record呢?我想是为了紧凑使用SSL的s3->wbuf缓冲区吧,该缓冲区事先建立,而且还 // 真不小:16K+!唉,真不觉得实现者想不出更好的办法了啊 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; }上面的函数调用执行到最后的return ssl3_write_pending(s,type,buf,len)前,就会得到下面的一共wb->left大小的缓冲区:
build_record { 操作SSL的s3->wbuf。我觉得好,就继续用 } write_raw { 往下层BIO写入SSL的s3->wbuf.buf的某一段 } do_ssl3_build { if (need_empty) { build_record; } build_record; ... }这样是不是比递归更清晰呢?至于那个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!我怕死无葬身之地,这个话题就此打住,who can who up!不过我要说一点,那就是polarssl的实现,看看人家的ssl_write接口:
ssl_write() { if( ssl->state != SSL_HANDSHAKE_OVER ) { handshack; } if (left) { flush_pending and return <=0 } build and write record, return num }这样调用逻辑会比较简单,更加清爽:
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 ); }看到这样的代码,我想怜香惜玉的人谁也不忍心加入if (0)逼着后来者用goto吧!