关闭

嵌入式 建立ssl连接过程分析六

485人阅读 评论(0) 收藏 举报
分类:

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的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。
0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:977382次
    • 积分:17615
    • 等级:
    • 排名:第540名
    • 原创:741篇
    • 转载:522篇
    • 译文:0篇
    • 评论:105条
    最新评论