`
totoxian
  • 浏览: 1034511 次
  • 性别: Icon_minigender_2
  • 来自: 西安
文章分类
社区版块
存档分类
最新评论

openssl框架闲谈--SSL实现

 
阅读更多

BIO 和EVP的一个应用就是SSL,没有SSL这个应用,BIO或者EVP只不过是一些底层的支撑接口,没有任何的现实意义,正是SSL使用了BIO和EVP 的机制提供了一个已经成型的安全套接字的实现策略。其实想象一下,安全套接字有两层含义,一层就是安全,这个由EVP接口实现了,另外一层含义就是套接 字,也就是说它必须是一个套接字,必须在操作的网络协议栈上进行IO,这一层含义是在BIO接口体现的,这个意义上,SSL正是通过组合BIO和EVP来 实现安全套接字的,BIO除了提供底层的抽象接口之外并不和SSL存在别的方面的耦合,因此BIO可以单独被使用,同样的,EVP也是可以单独被使用的。
不过,继续我们美妙的旅程之前首先要说的一点是,SSL本身就是一个BIO类型,并且是属于过滤类型的,在它的下层必须有一个socket类型的源/目的类型的BIO,在openssl中自带的sconnect实例中体现了这一点,创建过程如下:
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
ssl_ctx=SSL_CTX_new(SSLv23_client_method());
ssl=SSL_new(ssl_ctx);
SSL_set_connect_state(ssl);
ssl_bio=BIO_new(BIO_f_ssl());
BIO_set_ssl(ssl_bio,ssl,BIO_CLOSE);
out=BIO_new(BIO_s_connect());
BIO_set_conn_hostname(out,host);
BIO_set_nbio(out,1);
out=BIO_push(ssl_bio,out); //将socket类型的BIO置于SSL类型的BIO之下,注意,BIO_push里面会调用BIO_ctrl
对于ssl_bio的BIO_ctrl,它的BIO_CTRL_PUSH命令实际上就是执行:
static long ssl_ctrl(BIO *b, int cmd, long num, void *ptr)
{
...
case BIO_CTRL_PUSH:
if ((b->next_bio != NULL) && (b->next_bio != ssl->rbio))
{
SSL_set_bio(ssl,b->next_bio,b->next_bio); //b->next_bio其实就是上面的out,它是一个
socket类型的BIO
CRYPTO_add(&b->next_bio->references,1,CRYPTO_LOCK_BIO);
}
break;
...
}
可 以看出,ssl类型的BIO并没有使用一般的过滤BIO的模式,在其write回调函数中继续调用 SSL_write(b->next_bio,...)的模式,而是直接在SSL_push中使用回调函数将这个socket类型的BIO设置为 SSL的wbio,这样的话就可以使用统一的SSL_write来发送数据了。因为openssl设计的正交化,它就很灵活,各种实现方式对于 openssl来说都是可能的。
openssl可以使用SSL和BIO两种方式实现SSL,如果使用BIO方式,那么就是上面说的这一种,最终的IO是要调用BIO_write和 BIO_read来进行的,用BIO实现的ssl关键在于在io之前必须设置好套接字BIO和ssl类型的BIO以及SSL结构体之间的联系,这是通过 BIO_set_ssl和BIO_push来实现的。
另外一种实现ssl的方式更加简单,大致流程如下:ssl = SSL_new (ctx)之后使用SSL_set_fd (ssl, sd),其中sd是使用socket函数创建的套接字文件描述符,之后就可以使用SSL_read和SSL_write进行io操作了。这种方式显然很简 单,这二种方式其实效果是一样的,一种是构建于BIO之上,另一种是构建于一个新的特殊的SSL之上,殊途同归。openssl对于ssl的实现很好的一 点就是将复杂的ssl协议的握手操作隐藏到了io的过程当中,不管哪种方式实现的ssl,最终都要调用sslX_write,比如ssl2_write, 这个函数中会判断握手是否已经完成,如果没有,那么就会调用该SSL结构的handshake_func回调函数,这个回调函数也可以手工调用,在 SSL_write之前首先调用SSL_accept或者SSL_connect来手工调用handshake_func函数,这个 handshake_func是在什么时候赋值给SSL结构的呢?可以在SSL_accept/connect中进行懒惰的赋值,也可以使用 BIO_ctrl进行手动赋值。
ssl的握手操作怎么实现的呢?其实和操作系统内核的TCP三次握手是一样的,通过一个状态机实现,其实你完全可以将ssl协议理解成协议栈的一部分,这 样的话,ssl的握手仅仅是比tcp更高层次的协议握手罢了,状态机的实现将握手过程和协议的其它部分隔离开来,握手的状态机转换过程被封装在了一个无限 循环之中。
openssl代码中最有意思的就是加密和解密算法了,相信大部分人都会被吸引的。可惜本系列文章不能大谈加密算法,只能简单说一下算法怎么结合到程序的 流程里面。简单的说,有两种方式在ssl中使用加密算法,第一种就是使用BIO接口,使用一个加密解密类型的BIO摞到一个套接字的BIO之上,在加密解 密的BIO之上再摞一个buffer类型的BIO就可以了,加密解密的BIO的作用就是数据加密解密,buffer类型的BIO的作用是添加协议头部和校 验,最下面的socket类型的BIO的作用就是将数据发往传输层。这种方式十分的和谐,并且和已有的协议栈相处得十分融洽,安全套接字层的意义也正是如 此。另外一种加密解密的使用方式就是隐藏这一切,直接使用SSL_write和SSL_read进行通信,每一个SSL结构体都有一个 EVP_CIPHER_CTX类型的字段,在SSL_write中实现加密,加密数据的缓冲区也在SSL结构中被分配,一个SSL结构体定义如下:
struct ssl_st
{
int version; //版本号
int type; //是服务器还是客户端
SSL_METHOD *method; //ssl的回调函数
#ifndef OPENSSL_NO_BIO //如果ssl使用BIO机制
BIO *rbio; //包含一个套接字,下同
BIO *wbio;
BIO *bbio;
#else //反之就提供一个缓冲区
char *rbio;
char *wbio;
char *bbio;
#endif
int rwstate;
int in_handshake; //是否正在握手
int (*handshake_func)();//握手函数,它其实在SSL_METHOD中被提供
...
STACK_OF(SSL_CIPHER) *cipher_list; //算法列表
STACK_OF(SSL_CIPHER) *cipher_list_by_id;
EVP_CIPHER_CTX *enc_read_ctx; //解密使用的上下文环境
...
struct ssl2_state_st *s2; //版本2的特定结构,缓冲区在内部被包含
struct ssl3_state_st *s3; //版本3的特定结构,缓冲区在内部被包含
...
EVP_CIPHER_CTX *enc_write_ctx; //加密使用的上下文环境
...
SSL_CTX *ctx; //此ssl的上下文环境
...
};
可 以看出这个SSL结构有点自成体系的味道,其实它本应该自成体系的,否则它就真成了BIO和EVP的demo了,所有的相关的东西都在SSL中,包括加 密,解密使用的数据结构,加密,解密使用的缓冲区,最底层的socket类型的BIO以及其他ssl协议相关联的概念,这套代码之所以叫做openssl 而不是openbio或者openevp的原因吧。按照这种方式使用加密和解密虽然可能没有BIO的方式直观,但是却很直接,SSL指针中随时可以取出加 密解密结构体然后操作同样位于SSL结构中的加密解密缓冲区,最后发送给同样位于SSL中的wbio或者从rbio接收数据。在SSL_write最终要 调用的do_ssl3_write函数中会有以下逻辑:
s->method->ssl3_enc->enc(s,1);
而这个>ssl3_enc是另一套回调函数集合,是关于加密和解密的,其中的enc回调函数对于ssl3就是ssl3_enc:
int ssl3_enc(SSL *s, int send)
{
SSL3_RECORD *rec;
EVP_CIPHER_CTX *ds;
...
const EVP_CIPHER *enc;
if (send)
{
ds=s->enc_write_ctx; //得到ssl的加密解密上下文环境
rec= &(s->s3->wrec); //得到ssl加密解密的缓冲区
enc=EVP_CIPHER_CTX_cipher(s->enc_write_ctx);
}
...
EVP_Cipher(ds,rec->data,rec->input,l); //加密,ds为上下文,后面的参数为输入和输出缓冲区
...
}
可 能是由于效率的原因,openssl采用了自成体系的SSL的方式而没有采用串联BIO的方式,但是我个人还是很看好串联BIO的方式的,串联的BIO实 际上串联了不同的过滤策略,你当然可以理解成封包规则,网络的分层协议不就是靠这种方式实现的吗?每一层的封装其实就是一个过滤器,就是一个过滤策略,用 BIO来理解网络封包再好不过了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics