2.15 SSL_write
SSL结构(structssl_st)中的s2,s3指针分别指向SSL2和SSL3的状态结构,这些状态结构中都有用于写的wbuf,写操作相对读操作要简单一些。
SSL_write()实现向SSL通道中写数据,应用程序只需要向里写入明文数据,SSL通道自动对这些数据进行加密封装。
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()函数进行详细说明,
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);
if ((s->s3->flags &SSL3_FLAGS_POP_BUFFER) && (s->wbio == s->bbio))
{
// 这个标志导致的操作更多的是实验性功能
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;
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()完成:
int ssl3_write_bytes(SSL *s, int type, const void *buf_, intlen)
{
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;
returni;
}
if ((i == (int)n) ||
(type ==SSL3_RT_APPLICATION_DATA &&
(s->mode& SSL_MODE_ENABLE_PARTIAL_WRITE)))
{
// 写完或允许只进行部分写时可以成功返回
s->s3->empty_fragment_done= 0;
returntot+i;
}
n-=i;
tot+=i;
}
}
do_ssl3_write()完成对应用层数据的SSL封装,再调用底层发送函数发送数据, 这是一个static的内部函数:
static int do_ssl3_write(SSL *s, int type, const unsigned char*buf,
unsigned intlen, 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;
// 还有没写完的数据时先写这些数据
if (s->s3->wbuf.left != 0)
return(ssl3_write_pending(s,type,buf,len));
if (s->s3->alert_dispatch)
{
// 要发送告警信息
i=ssl3_dispatch_alert(s);
if (i <= 0)
return(i);
}
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);
if (!clear && !create_empty_fragment&& !s->s3->empty_fragment_done)
{
if(s->s3->need_empty_fragments && type ==SSL3_RT_APPLICATION_DATA)
{
// 需要空的碎片段的情况
// 以len为0,create_empty_fragment为1递归调用本函数建立空碎片数据,
// 基本就只是IV,供后续的实际数据使用
prefix_len =do_ssl3_write(s, type, buf, 0, 1);
if(prefix_len <= 0)
gotoerr;
if(s->s3->wbuf.len < (size_t)prefix_len +SSL3_RT_MAX_PACKET_SIZE)
{
// 发送缓冲区大小检查
SSLerr(SSL_F_DO_SSL3_WRITE,ERR_R_INTERNAL_ERROR);
gotoerr;
}
}
// 设置进行了空碎片操作标志
s->s3->empty_fragment_done= 1;
}
// 具体的要发送的网络数据指针, wb=&(s->s3->wbuf)
p = wb->buf + prefix_len;
// 类型
*(p++)=type&0xff;
// 写记录的类型
wr->type=type;
// 版本号
*(p++)=(s->version>>8);
*(p++)=s->version&0xff;
// 长度,先在保留指针位置,最后数据处理完才写具体长度
plen=p;
p+=2;
// 写记录的基本数据
wr->data=p;
wr->length=(int)len;
// 写记录的输入就是原始输入数据
wr->input=(unsigned char *)buf;
if (s->compress != NULL)
{
// 需要压缩的话先对数据进行压缩,明文压缩率才比较大,密文的压缩率几乎为0
if (!do_compress(s))
{
SSLerr(SSL_F_DO_SSL3_WRITE,SSL_R_COMPRESSION_FAILURE);
gotoerr;
}
}
else
{
// 不压缩就直接把输入数据拷贝到输出记录缓冲区
memcpy(wr->data,wr->input,wr->length);
wr->input=wr->data;
}
if (mac_size != 0)
{
// 计算认证码
s->method->ssl3_enc->mac(s,&(p[wr->length]),1);
// 将认证码长度添加到数据总长上,注意是对明文或压缩后的明文进行认证
// 而不是对密文进行认证
wr->length+=mac_size;
wr->input=p;
wr->data=p;
}
// 对数据进行加密, 对写数据加密是不会出错的
s->method->ssl3_enc->enc(s,1);
// 写入实际加密后数据的长度
s2n(wr->length,plen);
// 写入记录的类型和总长
wr->type=type;
wr->length+=SSL3_RT_HEADER_LENGTH;
if (create_empty_fragment)
{
// 如果是空碎片,直接就返回了,不实际发送
return wr->length;
}
// 实际的要发送的原始数据
wb->left = prefix_len + wr->length;
wb->offset = 0;
// 要发送的数据长度
s->s3->wpend_tot=len;
// 要发送明文的缓冲区, 实际是不发送的, 实际发送的是wb指向的缓冲区
s->s3->wpend_buf=buf;
// 数据类型
s->s3->wpend_type=type;
// 如果发送成功返回的数据长度, 这是指明文数据的长度, 供应用层程序判断用的
// 不是实际发送的压缩加密后的数据长度
s->s3->wpend_ret=len;
// 调用ssl3_write_pending()发送数据
return ssl3_write_pending(s,type,buf,len);
err:
return -1;
}
ssl3_write_pending()完成实际的数据发送, 这也是个static的函数, 和和普通write()一样,这个函数可能会阻塞, 而如果套接字是NON_BLOCK的发送不出去会直接返回:
static int ssl3_write_pending(SSL *s, int type, const unsigned char*buf,
unsigned int len)
{
int i;
// 判断数据长度是否出错用的是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]),
(unsignedint)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结构:
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, badreference count/n");
abort();
}
#endif
// 释放加密库所需附加数据
CRYPTO_free_ex_data(CRYPTO_EX_INDEX_SSL, s,&s->ex_data);
if (s->bbio != NULL)
{
// 释放BIO缓冲
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);
// 释放加密库链表
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);
// 清除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);
// 释放加密算法上下文
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的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。