msn: [email protected]
来源:http://yfydz.cublog.cn
2.15 SSL_write SSL结构(struct ssl_st)中的s2,s3指针分别指向SSL2和SSL3的状态结构,这些状态结构中都有用于写的wbuf,写操作相对读操作要简单一些。 SSL_write()实现向SSL通道中写数据,应用程序只需要向里写入明文数据,SSL通道自动对这些数据进行加密封装。 /* ssl/ssl_lib.c */ int SSL_write(SSL *s,const void *buf,int num) { if (s->handshake_func == 0) { SSLerr(SSL_F_SSL_WRITE, SSL_R_UNINITIALIZED); return -1; } // 发现发送shutdown标志,发送失败 if (s->shutdown & SSL_SENT_SHUTDOWN) { s->rwstate=SSL_NOTHING; SSLerr(SSL_F_SSL_WRITE,SSL_R_PROTOCOL_IS_SHUTDOWN); return(-1); } // 调用具体方法的发送函数, 如ssl3_write(), ssl2_write()等 return(s->method->ssl_write(s,buf,num)); } 下面以ssl3_write()函数进行详细说明, /* ssl/s3_lib.c */ int ssl3_write(SSL *s, const void *buf, int len) { int ret,n; #if 0 if (s->shutdown & SSL_SEND_SHUTDOWN) { s->rwstate=SSL_NOTHING; return(0); } #endif // 和read操作类似的一些检查工作 clear_sys_error(); if (s->s3->renegotiate) ssl3_renegotiate_check(s); /* This is an experimental flag that sends the * last handshake message in the same packet as the first * use data - used to see if it helps the TCP protocol during * session-id reuse */ /* The second test is because the buffer may have been removed */ if ((s->s3->flags & SSL3_FLAGS_POP_BUFFER) && (s->wbio == s->bbio)) { // 这个标志导致的操作更多的是实验性功能 /* First time through, we write into the buffer */ if (s->s3->delay_buf_pop_ret == 0) { ret=ssl3_write_bytes(s,SSL3_RT_APPLICATION_DATA, buf,len); if (ret <= 0) return(ret); s->s3->delay_buf_pop_ret=ret; } s->rwstate=SSL_WRITING; n=BIO_flush(s->wbio); if (n <= 0) return(n); s->rwstate=SSL_NOTHING; /* We have flushed the buffer, so remove it */ ssl_free_wbio_buffer(s); s->s3->flags&= ~SSL3_FLAGS_POP_BUFFER; ret=s->s3->delay_buf_pop_ret; s->s3->delay_buf_pop_ret=0; } else { // 正常的SSL3写数据,类型为SSL3_RT_APPLICATION_DATA,应用层数据 ret=ssl3_write_bytes(s,SSL3_RT_APPLICATION_DATA, buf,len); if (ret <= 0) return(ret); } return(ret); } 写数据操作主要由ssl3_write_bytes()完成: /* ssl/s3_pkt.c */ /* Call this to write data in records of type 'type' * It will return <= 0 if not all data has been sent or non-blocking IO. */ int ssl3_write_bytes(SSL *s, int type, const void *buf_, int len) { const unsigned char *buf=buf_; unsigned int tot,n,nw; int i; // 状态参数初始化 s->rwstate=SSL_NOTHING; // s3->wnum是写缓冲区中还没写完的数据长度 tot=s->s3->wnum; s->s3->wnum=0; if (SSL_in_init(s) && !s->in_handshake) { // 检查是否需要协商 i=s->handshake_func(s); if (i < 0) return(i); if (i == 0) { SSLerr(SSL_F_SSL3_WRITE_BYTES,SSL_R_SSL_HANDSHAKE_FAILURE); return -1; } } // 实际要写的数据量 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; } } do_ssl3_write()完成对应用层数据的SSL封装,再调用底层发送函数发送数据, 这是一个static的内部函数: /* ssl/s3_pkt.c */ 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) return(ssl3_write_pending(s,type,buf,len)); /* If we have an alert to send, lets send it */ if (s->s3->alert_dispatch) { // 要发送告警信息 i=ssl3_dispatch_alert(s); if (i <= 0) return(i); /* if it went, fall through and send more stuff */ } if (len == 0 && !create_empty_fragment) return 0; // wr为写的数据记录 wr= &(s->s3->wrec); // wb指向要写的数据缓冲 wb= &(s->s3->wbuf); sess=s->session; if ( (sess == NULL) || (s->enc_write_ctx == NULL) || (s->write_hash == NULL)) clear=1; // 实际发送的数据总长要追加的认证码长度 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) */ // 以len为0,create_empty_fragment为1递归调用本函数建立空碎片数据, // 基本就只是IV,供后续的实际数据使用 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=&(s->s3->wbuf) p = wb->buf + prefix_len; /* write the header */ // 类型 *(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) { // 需要压缩的话先对数据进行压缩,明文压缩率才比较大,密文的压缩率几乎为0 if (!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 */ // 如果是空碎片,直接就返回了,不实际发送 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; // 要发送明文的缓冲区, 实际是不发送的, 实际发送的是wb指向的缓冲区 s->s3->wpend_buf=buf; // 数据类型 s->s3->wpend_type=type; // 如果发送成功返回的数据长度, 这是指明文数据的长度, 供应用层程序判断用的 // 不是实际发送的压缩加密后的数据长度 s->s3->wpend_ret=len; /* we now just need to write the buffer */ // 调用ssl3_write_pending()发送数据 return ssl3_write_pending(s,type,buf,len); err: return -1; } ssl3_write_pending()完成实际的数据发送, 这也是个static的函数, 和和普通write()一样, 这个函数可能会阻塞, 而如果套接字是NON_BLOCK的发送不出去会直接返回: /* if s->s3->wbuf.left != 0, we need to call this */ static int ssl3_write_pending(SSL *s, int type, const unsigned char *buf, unsigned int len) { int i; /* XXXX */ // 判断数据长度是否出错用的是wpend_buf if ((s->s3->wpend_tot > (int)len) || ((s->s3->wpend_buf != buf) && !(s->mode & SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)) || (s->s3->wpend_type != type)) { SSLerr(SSL_F_SSL3_WRITE_PENDING,SSL_R_BAD_WRITE_RETRY); return(-1); } for (;;) // 循环直到全部数据发送完 { clear_sys_error(); if (s->wbio != NULL) { s->rwstate=SSL_WRITING; // 实际进行BIO写操作的是s3->wbuf中的数据,这是已经进行了压缩加密了的数据 i=BIO_write(s->wbio, (char *)&(s->s3->wbuf.buf[s->s3->wbuf.offset]), (unsigned int)s->s3->wbuf.left); } else { SSLerr(SSL_F_SSL3_WRITE_PENDING,SSL_R_BIO_NOT_SET); i= -1; } if (i == s->s3->wbuf.left) { s->s3->wbuf.left=0; s->rwstate=SSL_NOTHING; // 发送完实际数据,返回的是原始明文数据的长度 return(s->s3->wpend_ret); } else if (i <= 0) return(i); s->s3->wbuf.offset+=i; s->s3->wbuf.left-=i; } } 2.16 SSL_free SSL_free()函数释放SSL结构: /* ssl/ssl_lib.c */ void SSL_free(SSL *s) { int i; if(s == NULL) return; // 加密锁引用减1 i=CRYPTO_add(&s->references,-1,CRYPTO_LOCK_SSL); #ifdef REF_PRINT REF_PRINT("SSL",s); #endif if (i > 0) return; #ifdef REF_CHECK if (i < 0) { fprintf(stderr,"SSL_free, bad reference count\n"); abort(); /* ok */ } #endif // 释放加密库所需附加数据 CRYPTO_free_ex_data(CRYPTO_EX_INDEX_SSL, s, &s->ex_data); if (s->bbio != NULL) { // 释放BIO缓冲 /* If the buffering BIO is in place, pop it off */ if (s->bbio == s->wbio) { s->wbio=BIO_pop(s->wbio); } BIO_free(s->bbio); s->bbio=NULL; } // 释放读BIO if (s->rbio != NULL) BIO_free_all(s->rbio); // 释放写BIO if ((s->wbio != NULL) && (s->wbio != s->rbio)) BIO_free_all(s->wbio); // 释放初始化缓冲区 if (s->init_buf != NULL) BUF_MEM_free(s->init_buf); /* add extra stuff */ // 释放加密库链表 if (s->cipher_list != NULL) sk_SSL_CIPHER_free(s->cipher_list); if (s->cipher_list_by_id != NULL) sk_SSL_CIPHER_free(s->cipher_list_by_id); /* Make the next call work :-) */ // 清除SSL的会话 if (s->session != NULL) { ssl_clear_bad_session(s); SSL_SESSION_free(s->session); } // 释放SSL的读写上下文 ssl_clear_cipher_ctx(s); // 释放证书 if (s->cert != NULL) ssl_cert_free(s->cert); /* Free up if allocated */ // 释放加密算法上下文 if (s->ctx) SSL_CTX_free(s->ctx); if (s->client_CA != NULL) sk_X509_NAME_pop_free(s->client_CA,X509_NAME_free); // 释放SSL方法 if (s->method != NULL) s->method->ssl_free(s); // 释放SSL结构本身 OPENSSL_free(s); } 3. 结论 SSL使用了非常简单的应用程序接口(API)就将SSL的复杂处理过程对上层应用程序透明化, 而且虽然是用C编写的, 但编程思想是绝对OO的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。 发表于: 2006-11-06,修改于: 2006-11-06 08:47,已浏览4621次,有评论8条 推荐 投诉 网友: 本站网友 时间:2007-04-19 17:10:32 IP地址:124.42.48.★ 我下载了一个openssl的源文件是openssl-0.9.8e.tar.gz,在 ssl/s3_srvr.c文件中没有SSL_METHOD *SSLv3_server_method(void)这个函数;然后我又下载了一个openssl-fips-1.1.1.tar.gz,这个压缩包的ssl/s3_srvr.c中有SSL_METHOD *SSLv3_server_method(void);请问,这两个有什么区别。 网友: yfydz 时间:2007-04-22 20:37:13 IP地址:61.149.250.★ 我分析的openssl版本在本系列第一篇里已经写了,有差异正常 后一文件没看过 网友: zhuzq 时间:2007-06-20 13:16:13 IP地址:221.10.7.★ 在这里: IMPLEMENT_ssl3_meth_func(SSLv3_server_method, ssl3_accept, ssl_undefined_function, ssl3_get_server_method) 网友: zhuzq 时间:2007-06-20 13:17:16 IP地址:221.10.7.★ 好多东东都是这样定义的 :) 网友: 本站网友 时间:2008-06-16 11:24:07 IP地址:222.66.155.★ 您好,请问用openssl来浏览https网页,需要证书吗?我自己测试了一下,只要SSL_connect成功,用SSL_write和SSL_read就可以下载页面,但不能确定是否所有网页都是这样的。 SSL_CTX_set_default_passwd_cb_userdata(ctx, pw); SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_ASN1); SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)SSL_CTX_check_private_key(ctx) 想请教你这几个证书相关的函数在https网页下载中是否必须?谢谢 网友: yfydz 时间:2008-06-18 21:25:53 IP地址:58.31.246.★ 看服务器设置,公共网站一般不需要 网友: 本站网友 时间:2009-11-27 20:23:05 IP地址:218.66.13.★ 有用过USB-KEY证书进行连接的情况吗?由于USB-KEY中证书的私钥无法导出,那建立连接的时候使用SSL_CTX_use_PrivateKey_file(),即用SSL_use_PrivateKey()或SSL_CTX_use_PrivateKey()时,如何传入私钥? 采用CryptoAPI 可获取到私钥的句柄HCRYPTKEY,但如何转换成EVP_PKEY类型呢?非常感谢~~