SSL类型的BIO
—根据openssldoc\crypto\bio_f_ssl.pod翻译和自己的理解写成
(作者:DragonKing,Mail:wzhah@263.net,发布于:之
openssl专业论坛)
从名字就可以看出,这是一个非常重要的BIO类型,它封装了openssl里面的ssl规则
和函数,相当于提供了一个使用SSL很好的有效工具,一个很好的助手。其定义(opens
sl\bio.h,openssl\ssl.h)如下:
BIO_METHOD*BIO_f_ssl(void);
#defineBIO_set_ssl(b,ssl,c)BIO_ctrl(b,BIO_C_SET_SSL,c,(char*)ssl)
#defineBIO_get_ssl(b,sslp)BIO_ctrl(b,BIO_C_GET_SSL,0,(char*)sslp)
#defineBIO_set_ssl_mode(b,client)BIO_ctrl(b,BIO_C_SSL_MODE,client,NUL
L)
#defineBIO_set_ssl_renegotiate_bytes(b,num)BIO_ctrl(b,BIO_C_SET_SSL_R
ENEGOTIATE_BYTES,num,NULL);
#defineBIO_set_ssl_renegotiate_timeout(b,seconds)BIO_ctrl(b,BIO_C_SET
_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL);
#defineBIO_get_num_renegotiates(b)BIO_ctrl(b,BIO_C_SET_SSL_NUM_RENEGO
TIATES,0,NULL);
BIO*BIO_new_ssl(SSL_CTX*ctx,intclient);
BIO*BIO_new_ssl_connect(SSL_CTX*ctx);
BIO*BIO_new_buffer_ssl_connect(SSL_CTX*ctx);
intBIO_ssl_copy_session_id(BIO*to,BIO*from);
voidBIO_ssl_shutdown(BIO*bio);
#defineBIO_do_handshake(b)BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL)
该类型BIO的实现文件在ssl\bio_ssl.c里面,大家可以参看这个文件得到详细的函
数实现信息。
【BIO_f_ssl】
该函数返回一个SSL类型的BIO_METHOD结构,其定义如下:
staticBIO_METHODmethods_sslp=
{
BIO_TYPE_SSL,"ssl",
ssl_write,
ssl_read,
ssl_puts,
NULL,/*ssl_gets,*/
ssl_ctrl,
ssl_new,
ssl_free,
ssl_callback_ctrl,
};
可见,SSL类型BIO不支持BIO_gets的功能。
BIO_read和BIO_write函数调用的时候,SSL类型的BIO会使用SSL协议进行底层的I/
O操作。如果此时SSL连接并没有建立,那么就会在调用第一个IO函数的时候先进行连接
的建立。
如果使用BIO_push将一个BIO附加到一个SSL类型的BIO上,那么SSL类型的BIO读写数
据的时候,它会被自动调用。
BIO_reset调用的时候,会调用SSL_shutdown函数关闭目前所有处于连接状态的SSL
,然后再对下一个BIO调用BIO_reset,这功能一般就是将底层的传输连接断开。调用完
成之后,SSL类型的BIO就处于初始的接受或连接状态。
如果设置了BIO关闭标志,那么SSL类型BIO释放的时候,,内部的SSL结构也会被SSL_
free函数释放。
【BIO_set_ssl】
该函数设置SSL类型BIO的内部ssl指针指向ssl,同时使用参数c设置了关闭标志。
【BIO_get_ssl】
该函数返回SSL类型BIO的内部的SSL结构指针,得到该指针后,可以使用标志的SSL
函数对它进行操作。
【BIO_set_ssl_mode】
该函数设置SSL的工作模式,如果参数client是1,那么SSL工作模式为客户端模式,
如果client为0,那么SSL工作模式为服务器模式。
【BIO_set_ssl_renegotiate_bytes】
该函数设置需要重新进行session协商的读写数据的长度为num。当设置完成后,在
没读写的数据一共到达num字节后,SSL连接就会自动重新进行session协商,这可以加强
SSL连接的安全性。参数num最少为512字节。
【BIO_set_ssl_renegotiate_timeout】
该函数跟上述函数一样都是为了加强SSL连接的安全性的。不同的是,该函数采用的
参数是时间。该函数设置重新进行session协商的时间,其单位是秒。当SSLsession连
接建立的时间到达其设置的时间时,连接就会自动重新进行session协商。
【BIO_get_num_renegotiates】
该函数返回SSL连接在因为字节限制或时间限制导致session重新协商之前总共读写
的数据长度。
【BIO_new_ssl】
该函数使用ctx参数所代表的SSL_CTX结构创建一个SSL类型的BIO,如果参数client
为非零值,就使用客户端模式。
【BIO_new_ssl_connect】
该函数创建一个包含SSL类型BIO的新BIO链,并在后面附加了一个连接类型的BIO。
方便而且有趣的是,因为在filter类型的BIO里,如果是该BIO不知道(没有实现)
BIO_ctrl操作,它会自动把该操作传到下一个BIO进行调用,所以我们可以在调用本函数
得到BIO上直接调用BIO_set_host函数来设置服务器名字和端口,而不需要先找到连接B
IO。
【BIO_new_buffer_ssl_connect】
创建一个包含buffer型的BIO,一个SSL类型的BIO以及一个连接类型的BIO。
【BIO_ssl_copy_session_id】
该函数将BIO链from的SSLSessionID拷贝到BIO链to中。事实上,它是通过查找到
两个BIO链中的SSL类型BIO,然后调用SSL_copy_session_id来完成操作的。
【BIO_ssl_shutdown】
该函数关闭一个BIO链中的SSL连接。事实上,该函数通过查找到该BIO链中的SSL类
型BIO,然后调用SSL_shutdown函数关闭其内部的SSL指针。
【BIO_do_handshake】
该函数在相关的BIO上启动SSL握手过程并建立SSL连接。连接成功建立返回1,否则
返回0或负值,如果连接BIO是非阻塞型的BIO,此时可以调用BIO_should_retry函数以决
定释放需要重试。如果调用该函数的时候SSL连接已经建立了,那么该函数不会做任何事
情。一般情况下,应用程序不需要直接调用本函数,除非你希望将握手过程跟其它IO操
作分离开来。
需要注意的是,如果底层是阻塞型(openssl帮助文档写的是非阻塞型,nonblocki
ng,但是根据上下文意思已经BIO的其它性质,我个人认为是阻塞型,blocking才是正确
的)的BIO,在一些意外的情况SSL类型BIO下也会发出意外的重试请求,如在执行BIO_r
ead操作的时候如果启动了session重新协商的过程就会发生这种情况。在0.9.6和以后的
版本,可以通过SSL的标志SSL_AUTO_RETRY将该类行为禁止,这样设置之后,使用阻塞型
传输的SSL类型BIO就永远不会发出重试的请求。
【例子】
1.一个SSL/TLS客户端的例子,完成从一个SSL/TLS服务器返回一个页面的功能。其
中IO操作的方法跟连接类型BIO里面的例子是相同的。
BIO*sbio,*out;
intlen;
chartmpbuf[1024];
SSL_CTX*ctx;
SSL*ssl;
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
//如果系统平台不支持自动进行随机数种子的设置,这里应该进行设置(seedPRN
G)
ctx=SSL_CTX_new(SSLv23_client_method());
//通常应该在这里设置一些验证路径和模式等,因为这里没有设置,所以该例子可
以跟使用任意CA签发证书的任意服务器建立连接
sbio=BIO_new_ssl_connect(ctx);
BIO_get_ssl(sbio,&ssl);
if(!ssl){
fprintf(stderr,"Can’tlocateSSLpointer\n");
}
/*不需要任何重试请求*/
SSL_set_mode(ssl,SSL_MODE_AUTO_RETRY);
//这里你可以添加对SSL的其它一些设置
BIO_set_conn_hostname(sbio,"localhost:https");
out=BIO_new_fp(stdout,BIO_NOCLOSE);
if(BIO_do_connect(sbio)<=0){
fprintf(stderr,"Errorconnectingtoserver\n");
ERR_print_errors_fp(stderr);
}
if(BIO_do_handshake(sbio)<=0){
fprintf(stderr,"ErrorestablishingSSLconnection\n");
ERR_print_errors_fp(stderr);
}
/*这里可以添加检测SSL连接的代码,得到一些连接信息*/
BIO_puts(sbio,"GET/HTTP/1.0\n\n");
for(;;){
len=BIO_read(sbio,tmpbuf,1024);
if(len<=0)break;
BIO_write(out,tmpbuf,len);
}
BIO_free_all(sbio);
BIO_free(out);
2.一个简单的服务器的例子。它使用了buffer类型的BIO,从而可以使用BIO_gets从
一个SSL类型的BIO读取数据。它创建了一个包含客户端请求的随机web页,并把请求信息
输出到标准输出设备。
BIO*sbio,*bbio,*acpt,*out;
intlen;
chartmpbuf[1024];
SSL_CTX*ctx;
SSL*ssl;
ERR_load_crypto_strings();
ERR_load_SSL_strings();
OpenSSL_add_all_algorithms();
//可能需要进行随机数的种子处理(seedPRNG)
ctx=SSL_CTX_new(SSLv23_server_method());
if(!SSL_CTX_use_certificate_file(ctx,"server.pem",SSL_FILETYPE_PEM)
||!SSL_CTX_use_PrivateKey_file(ctx,"server.pem",SSL_FILETYPE_PEM)
||!SSL_CTX_check_private_key(ctx)){
fprintf(stderr,"ErrorsettingupSSL_CTX\n");
ERR_print_errors_fp(stderr);
return0;
}
//可以在这里设置验证路径,DH和DSA算法的临时密钥回调函数等等
/*创建一个新的服务器模式的SSL类型BIO*/
sbio=BIO_new_ssl(ctx,0);
BIO_get_ssl(sbio,&ssl);
if(!ssl){
fprintf(stderr,"Can’tlocateSSLpointer\n");
}
/*不需要任何重试请求*/
SSL_set_mode(ssl,SSL_MODE_AUTO_RETRY);
/*创建一个Buffer类型BIO*/
bbio=BIO_new(BIO_f_buffer());
/*加到BIO链上*/
sbio=BIO_push(bbio,sbio);
acpt=BIO_new_accept("4433");
/*
当一个新连接建立的时候,我们可以将sbio链自动插入到连接所在的BIO链中去。
这时候,这个BIO链(sbio)就被accept类型BIO吞并了,并且当accept类型BIO释放的时候
,它会自动被释放。
*/
BIO_set_accept_bios(acpt,sbio);
out=BIO_new_fp(stdout,BIO_NOCLOSE);
/*设置acceptBIO*/
if(BIO_do_accept(acpt)<=0){
fprintf(stderr,"ErrorsettingupacceptBIO\n");
ERR_print_errors_fp(stderr);
return0;
}
/*等待连接的建立*/
if(BIO_do_accept(acpt)<=0){
fprintf(stderr,"Errorinconnection\n");
ERR_print_errors_fp(stderr);
return0;
}
/*
因为我们只想处理一个连接,所以可以删除和释放acceptBIO了
*/
sbio=BIO_pop(acpt);
BIO_free_all(acpt);
if(BIO_do_handshake(sbio)<=0){
fprintf(stderr,"ErrorinSSLhandshake\n");
ERR_print_errors_fp(stderr);
return0;
}
BIO_puts(sbio,"HTTP/1.0200OK\r\nContent-type:text/html\r\n\r\n");
BIO_puts(sbio,"<pre>\r\nConnectionEstablished\r\nRequestheaders:\r\n
");
BIO_puts(sbio,"————————————————–\r\n"
);
for(;;){
len=BIO_gets(sbio,tmpbuf,1024);
if(len<=0)break;
BIO_write(sbio,tmpbuf,len);
BIO_write(out,tmpbuf,len);
/*查找请求头的结束标准空白行*/
if((tmpbuf[0]==’\r’)||(tmpbuf[0]==’\n’))break;
}
BIO_puts(sbio,"————————————————–\r\n"
);
BIO_puts(sbio,"</pre>\r\n");
/*因为使用了buffer类型的BIO,我们最好调用BIO_flush函数*/
BIO_flush(sbio);
BIO_free_all(sbio);
为了一些琐事吵架,然后冷战,疯狂思念对方,最后和好。