SSL连接建立过程分析(6)

摘要:
SSL连接建立过程分析本文档的Copyleft属于yfydz,使用GPL发布。它可以自由复制和复制。复制时请保持文件的完整性。严禁将其用于任何商业目的。write()实现将数据写入SSL通道。应用程序只需要将明文数据写入SSL通道,SSL通道会自动加密和封装这些数据。
SSL连接建立过程分析(6)
 
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源: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的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。

免责声明:文章转载自《SSL连接建立过程分析(6)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇vim语法高亮插件编写devexpress打印gridControl下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

charles 手机证书下载安装

本文参考:charles 手机证书下载安装 本文的Charles,适应windows/MAC/IOS/Android,避免抓包HTTPS失败和乱码; 用的版本是V4.1.2,其它版本原理类似; charles如果不配置SSL通用证书; 会导致HPPTS协议的域名抓取失败/乱码的现象; 现在SSL越来越多,很多博客都上了SSL,支付相关的行业更是基础配置;...

Linux下Apache配置HTTPS功能

Apache配置HTTPS功能转https://www.cnblogs.com/liaojiafa/p/6028816.html 一、yum 安装openssl和openssl-devel,httpd-devel 二、生成证书(也可以从公司的证书颁发机构获取): #建立服务器密钥 openssl genrsa -des3 1024 > /...

网站开启https后加密协议始终是TLS1.0如何配置成TLS1.2?

网站开启https后加密协议始终是TLS1.0如何配置成TLS1.2?   要在服务器上开启 TLSv1.2,通常要求基于 OpenSSL 环境的,使用 OpenSSL 1.0+ ,推荐 OpenSSL 1.0.1+。 要求基于 Java 环境的,使用 Jdk 1.7+ 。 查看openssl的版本: # openssl version...

基于openresty的https配置实践

最近机器人项目的子项目,由于和BAT中的一家进行合作,人家要求用HTTPS连接,于是乎,我们要改造我们的nginx的配置,加添HTTPS的支持。 当然了,HTTPS需要的证书,必须是认证机构颁发的,这里的配置实践,也是从技术路线上的一次操作,证书是基于openssl生成的。没有谁颁发,自建得之! 不多说,开始实践!!!! 1. openssl的版本信息 [...

requests.exceptions.SSLError: HTTPSConnectionPool(host='cn.bing.com', port=443)报错解决方案

一、问题描述 运行以下代码 #!/usr/local/bin/python3.7 import requests import ssl # 请求url url = 'https://cn.bing.com/tlookupv3?isVertical=1&&IG=1E1AE90B09BB41E28506E0ADC9E45704&II...

C Primer Plus(十五)

第十五章 位操作 15.1 二进制数、位和字节 以2为基数表示的数字称为二进制数,可以使用二进制数将任何整数表示为1和0的一个组合,这种系统非常适合于数字计算机使用。 15.1.1 二进制整数 描述存储器芯片和数据传输率时使用的字节指8位字节。最小的二进制数是00000000,或一个简单的0.一个字节可以存储的数的范围是0到255.通过改变对位模式的解释方式...