zoukankan      html  css  js  c++  java
  • SSL连接建立过程分析(6)

    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的, 将各种对象都完整的封装起来, 只使用其外部接口而不用考虑其内部实现。
  • 相关阅读:
    读书笔记4瞬时相应网站的高性能架构
    读书笔记3大型网站的架构要素
    读书笔记2大型网站的架构模式
    可用的rtmp卫视直播地址
    html中的特殊符号表示法
    深入理解JS原型链与继承
    css3 media媒体查询器用法总结
    HTML条件注释用法诠释
    jQuery中$符号冲突问题 (转载)
    JS 输出对象的属性以及方法[转载]
  • 原文地址:https://www.cnblogs.com/adylee/p/1541095.html
Copyright © 2011-2022 走看看