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

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
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类型呢?非常感谢~~

你可能感兴趣的:(数据结构,C++,c,网络应用,C#)