SSL连接建立过程分析(6)
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 ) */
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的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。