Openssl之BIO系列(3)

17.连接(connect)类型BIO

该类型的BIO封装了socketConnect方法,它使得编程的时候可以使用统一的BIO规则进行socketconnect连接的操作和数据的发送接受,而不用关心具体平台的Socketconnect方法的区别。其相关定义的一些函数如下(openssl\bio.h)

BIO_METHOD * BIO_s_connect(void);

#define BIO_set_conn_hostname(b, name) BIO_ctrl(b, BIO_C_SET_CONNECT, 0, (char *) name)

#define BIO_set_conn_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,1,(char *)port)

#define BIO_set_conn_ip(b,ip) BIO_ctrl(b,BIO_C_SET_CONNECT,2,(char *)ip)

#define BIO_set_conn_int_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,3,(char *)port)

#define BIO_get_conn_hostname(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,0)

#define BIO_get_conn_port(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,1)

#define BIO_get_conn_ip(b,ip) BIO_ptr_ctrl(b,BIO_C_SET_CONNECT,2)

#define BIO_get_conn_int_port(b,port) BIO_int_ctrl(b,BIO_C_SET_CONNECT,3,port)

#define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL)

#define BIO_do_connect(b) BIO_do_handshake(b)

BIO *BIO_new_connect(char *str)

17.1 BIO_s_connect

该函数返回一个connect类型的BIO_METHOD结构,该结构定义如下:

static BIO_METHOD methods_connectp=

{

BIO_TYPE_CONNECT,

"socket connect",

conn_write,

conn_read,

conn_puts,

NULL, /* connect_gets, */

conn_ctrl,

conn_new,

conn_free,

conn_callback_ctrl,

};

事实上,为了维护一个Socket结构,openssl里面还定义了一个BIO_CONNECT结构来维护底层socket的地址信息以及状态信息,不过,通过封装,我们一般是不用直接接触该结构的,在此也就不再多做介绍,感兴趣可以参看文件bss_conn.c里面的定义和函数。

BIO_readBIO_write的操作调用底层的连接的IO操作来完成。如果在服务器地址和端口设置正确,但连接没有建立的时候调用读写操作函数,那么会先进行连接的建立操作,然后再进行读写操作。

BIO_puts操作是支持的,但是BIO_gets操作不支持,这在该类型BIOBIO_METHOD结构定义中就可以看出来。

如果关闭标志设置了,那么在BIO被释放的时候,任何活动的连接和socket都会被关闭。

BIO_reset方法被调用的时候,连接(connect)类型的BIO的任何活动连接都会被关闭,从而回到可以重新跟同样的主机建立连接的状态。

BIO_get_fd函数返回连接类型的BIO的底层socket,当参数c不是NULL的时候,就将该socket赋值给c,当然,socket也作为返回值。c参数应该为int*类型。如果BIO没有初始化,则返回-1

17.2 BIO_set_conn_hostname

该函数使用字符串设置主机名,该主机名也可以为IP地址的形式,还可以包括端口号,如hostname:porthostname/any/other/pathhostname:port/any/other/path也是可以的。返回1

17.3 BIO_set_conn_port

该函数设置主机的端口号。该端口号的形式可以为数字的形式,也可以为字符串类似"http"的形式。如果使用字符串形式,首先会使用getservbyname函数搜索其相关的端口,如果没有搜索到,那么就会使用一张缺省的名字端口解释表,目前该表列出的字符串有:http, telnet, socks, https, ssl, ftp, gopher  wais.返回1

需要注意的是:如果端口名已经作为主机名的一部分设置了,那么它就会覆盖BIO_set_conn_port函数设置的端口值。有的时候(如有些应用可能不希望用固定的端口连接)可能不方便,这时候可以通过检测输入主机名的字符串中的":"字符,报错或截取字符串来避免这种情况。

17.4 BIO_set_conn_ip

该函数使用二进制的模式设置IP地址。返回1

17.5 BIO_set_conn_int_port

该函数以整数形式设置主机端口号,参数应该为int*的形式。返回1

17.6 BIO_get_conn_hostname

该函数返回连接类型BIO的主机名,如果BIO以及初始化,但是没有设置主机名,那么返回NULL。返回值因为是一个内部指针,所有不能更改它的值。

17.7 BIO_get_conn_port

该函数返回字符串类型的端口信息。如果没有设置,就返回NULL

17.8 BIO_get_conn_ip

该函数返回二进制形式的IP地址。如果没有设置,返回为全0

17.9 BIO_get_conn_int_port

该函数返回整数形式的端口号,如果没有设置,则返回0

上述四个函数的返回值在连接操作完成之后会被更新。而在此之前,返回值都是应用程序自己设置的。

17.10 BIO_set_nbio

设置I/O的非阻塞标志。如果参数n0,则I/O设置为阻塞模式;如果n1,则I/O设置为非阻塞模式。缺省的模式是阻塞模式。应该在连接建立之前调用本函数,因为非阻塞模式的I/O是在连接过程中设置的。返回值恒为1

注意:如果是阻塞模式的I/O,执行IO操作时(如读写),如果返回负值,说明就产生了错误的情况,如果返回值是0,一般来说表明连接已经关闭。如果设置为非阻塞模式,那么发出重试的请求就是很正常的事情了。

17.11 BIO_do_connect

该函数进行给定BIO的连接操作,如果连接成功,返回1,否则返回0或负值。在非阻塞模式的时候,如果调用失败了,可以调用BIO_should_retry函数以决定是否需要重试。

一般来说,应用程序不需要调用本函数,只有在希望将连接过程跟其它IO处理过程独立开来的时候,才需要调用本函数。

在初始化连接的过程的时候,如果返回值失败的原因为BIO_RR_CONNECT,调用BIO_should_io_special返回值可能也为true。如果出现这种情况,说明连接过程被阻塞住了,应用程序应该使用正常的方法进行处理,直到底层的socket连接上了再重试。

17.12 BIO_new_connect

该函数创建并返回一个连接类型的BIO,其实,它调用了BIO_s_connectBIO_new已经BIO_set_conn_hostname函数完成了整个操作。成功则返回一个BIO,否则返回NULL

例子:这是一个连接到本地Web服务器的例子,返回一页的信息并把该信息复制到标准输出设备。

BIO *cbio, *out;

int len;

char tmpbuf[1024];

ERR_load_crypto_strings();

cbio = BIO_new_connect("localhost:http");

out = BIO_new_fp(stdout, BIO_NOCLOSE);

if(BIO_do_connect(cbio) <= 0)

{

fprintf(stderr, "Error connecting to server\n");

ERR_print_errors_fp(stderr);

/* whatever ... */

}

BIO_puts(cbio, "GET / HTTP/1.0\n\n");

for(;;)

{

len = BIO_read(cbio, tmpbuf, 1024);

if(len <= 0) break;

BIO_write(out, tmpbuf, len);

}

BIO_free(cbio);

BIO_free(out);

18.接受(accept)类型BIO

接受(accept)类型的BIO跟连接(connect)类型BIO是相对应的,它封装了Socketaccept方法及其相关的一些操作,使得能够对不同的平台使用同一的函数进行操作。其定义的相关函数如下(openssl\bio.h)

BIO_METHOD * BIO_s_accept(void);

#define BIO_set_accept_port(b,name) BIO_ctrl(b,BIO_C_SET_ACCEPT,0,(char *)name)

#define BIO_get_accept_port(b) BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,0)

BIO *BIO_new_accept(char *host_port);

#define BIO_set_nbio_accept(b,n) BIO_ctrl(b,BIO_C_SET_ACCEPT,1,(n)?"a":NULL)

#define BIO_set_accept_bios(b,bio) BIO_ctrl(b,BIO_C_SET_ACCEPT,2,(char *)bio)

#define BIO_set_bind_mode(b,mode) BIO_ctrl(b,BIO_C_SET_BIND_MODE,mode,NULL)

#define BIO_get_bind_mode(b,mode) BIO_ctrl(b,BIO_C_GET_BIND_MODE,0,NULL)

#define BIO_BIND_NORMAL 0

#define BIO_BIND_REUSEADDR_IF_UNUSED 1

#define BIO_BIND_REUSEADDR 2

#define BIO_do_accept(b) BIO_do_handshake(b)

18.1 BIO_s_accept

该函数返回一个接受(Accept)类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_acceptp=

{

BIO_TYPE_ACCEPT,

"socket accept",

acpt_write,

acpt_read,

acpt_puts,

NULL, /* connect_gets, */

acpt_ctrl,

acpt_new,

acpt_free,

NULL,

};

通过该结构我们可以一目了然的知道该方法支持什么I/O操作,在本类型中,BIO_gets的操作是不支持的。跟连接(connect)类型BIO一样,openssl也定义了一个维护接受Socket信息的结构,在此我也不再详细介绍该结构,大家参考bss_acpt.c文件。

本类型的BIO对各种平台的TCP/IPaccept做了封装,所以在使用的时候就可以同一的使用BIO的规则进行操作,而不用担心因为不同的平台带来程序改写或增加移植的工作量,这也是BIO很重要的一个目的。

BIO_readBIO_write函数操作调用了底层平台连接的I/O相关操作,如果这时候没有连接建立,端口设置正确,那么该BIO就会等待连接的建立。事实上,当一个连接建立的时候,一个新的socket类型BIO就会被创建并附加到BIO链中,形成accept->socketBIO结构,所以这时候对初始化了的接受socket进行IO操作就会导致它处于等待连接建立的状态。当一个接受类型的BIOBIO链的末尾的时候,在处理I/O调用之前它会先等待一个连接的建立;如果不是在末尾,那么它简单的把I/O调用传到下一个BIO

如果接受(accept)类型BIO的关闭标志设置了,那么当BIO被释放的时候,该BIO链上任何活动的连接和socket都会被关闭。

BIO_get_fdBIO_set_fd可以用来取得和设置该连接的socket描述符,详细情况参看“12.描述符(fd) 类型BIO”。

18.2 BIO_set_accept_port

该函数使用字串名来设置接受端口。其形式为“host:port”,“host”是要使用的接口,“port”是端口。这两部分都可以为“*”,这时候表示可以使用任意接口和端口。这里的端口的字符串含义跟连接(connect)类型BIO里面定义的一样,可以为数字形式的,可以使用getservbyname函数去匹配得到,还可以使用字符串的表,请参看连接型BIO说明的相关部分。

18.3 BIO_new_accept

该函数将BIO_newBIO_set_accept_port函数放在一个函数里面调用,创建一个新的接受(accept)类型的BIO

18.4 BIO_set_nbio_accept

该函数设置接受socket是否为阻塞模式(缺省),如果参数n0,为阻塞模式,n1则为非阻塞模式。

18.5 BIO_set_accept_bios

该函数用来设置一个BIO链,当接受到一个连接的时候,这个设置好的BIO链就会被复制或附加到整个BIO链上来。有的时候这中处理方法是非常好的,比如,如果每个连接都需要一个缓冲区或SSL类型的BIO,这时候使用本函数就省了很多麻烦了。需要注意的是,在调用本函数后,这个设置的链上的BIO不能自己释放,在接受(acceptBIO被释放后,它们会自动释放。

当该函数调用的时候,其设置的BIO链位于接受BIOsocket类型的BIO之间,即形成:accept->otherbios->socket的新的BIO链。

18.6 BIO_set_bind_modeBIO_get_bind_mode

这两个函数用来设置和取得目前的绑定模式。如果设置为BIO_BIND_NORMAL(缺省),那么另外一个socket就不能绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR,那么另外的socket可以绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR_IF_UNUSED,那么首先会尝试以BIO_BIND_NORMAL的模式绑定到端口,如果失败了而且端口没有使用,那么就会使用BIO_BIND_REUSEADDR模式绑定到端口。

18.7 BIO_do_accept

该函数有两个功能,当它在接受(acceptBIO设置好之后第一被调用的时候,它会创建一个接受socket并把它跟地址绑定;第二次被调用的时候,它会等待连接的到来。

注意:如果服务器端希望可以处理多个连接的情况(通常都是这样的),那么接受BIO必须能够用来处理以后的连接,这时候可以这样做:等待到一个连接后,调用

connection=BIO_pop(accept)

这样,connection会包含一个最近建立的连接的BIOaccept就再次成为一个独立的BIO,可以用来处理其它连接了。如果没有其它连接建立,那么就可以使用BIO_free释放该BIO

当然,如果只有一个连接需要处理,也可以使用连接BIO本身来进行I/O操作。但是一般来说不推荐这样做,因为这时候该接受BIO依然处于接受其它连接建立的状态。这可以使用上述的方法解决。

例子:这个例子在4444端口接受了两个连接,发送信息到每个连接上然后释放他们。

BIO *abio, *cbio, *cbio2;

ERR_load_crypto_strings();

abio = BIO_new_accept("4444");

/* 首先调用BIO_accept启动接受BIO */

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error setting up accept\n");

ERR_print_errors_fp(stderr);

exit(0);

}

/* 等待连接建立*/

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error accepting connection\n");

ERR_print_errors_fp(stderr);

exit(0);

}

fprintf(stderr, "Connection 1 established\n");

/* 返回连接的BIO*/

cbio = BIO_pop(abio);

BIO_puts(cbio, "Connection 1: Sending out Data on initial connection\n");

fprintf(stderr, "Sent out data on connection 1\n");

/* 等待另一个连接的建立 */

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error accepting connection\n");

ERR_print_errors_fp(stderr);

exit(0);

}

fprintf(stderr, "Connection 2 established\n");

/* 关闭连接BIO,不再接受连接的建立 */

cbio2 = BIO_pop(abio);

BIO_free(abio);

BIO_puts(cbio2, "Connection 2: Sending out Data on second\n");

fprintf(stderr, "Sent out data on connection 2\n");

BIO_puts(cbio, "Connection 1: Second connection established\n");

/* 关闭这两个连接 */

BIO_free(cbio);

BIO_free(cbio2);

19.Fileter类型的NULLBIO

前面我们已经介绍完source/sink型的BIO了,以后的BIO系列文章将开始介绍过滤(filter)类型的BIO。那么首先介绍的是一个非常简单的BIO类型——NULLfilter BIO,其定义如下(openssl\bio.h):

BIO_METHOD * BIO_f_null(void);

在本类型中,只有这个函数是定义了的,该函数返回一个NULL型的过滤BIO_METHOD结构,NULL过滤型BIO是一个不作任何事情的BIO。针对该类型BIO的任何调用都会被简单传递中BIO链中的下一个BIO中去,也就相当于该BIO是不存在的一样。所以,一般来说,该类型的BIO用处不大。

20.缓冲(buffer)类型BIO

缓冲(buffer)类型BIO是一种过滤(filter)型的BIO,其相关的一些函数定义如下(openssl\bio.h):

BIO_METHOD * BIO_f_buffer(void);

#define BIO_get_buffer_num_lines(b) BIO_ctrl(b,BIO_C_GET_BUFF_NUM_LINES,0,NULL)

#define BIO_set_read_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,0)

#define BIO_set_write_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,1)

#define BIO_set_buffer_size(b,size) BIO_ctrl(b,BIO_C_SET_BUFF_SIZE,size,NULL)

#define BIO_set_buffer_read_data(b,buf,num) BIO_ctrl(b,BIO_C_SET_BUFF_READ_DATA,num,buf)

20.1 BIO_f_buffer

该函数返回一个Buffer类型的BIO_METHOD结构,该结构定义如下(bf_buff.c):

static BIO_METHOD methods_buffer=

{

BIO_TYPE_BUFFER,

"buffer",

buffer_write,

buffer_read,

buffer_puts,

buffer_gets,

buffer_ctrl,

buffer_new,

buffer_free,

buffer_callback_ctrl,

};

由结构定义可见,该类型BIO支持所有BIOI/O函数。写入缓冲(buffer)BIO的数据存储在缓冲区里面,定期写入到BIO链的下一个BIO中,事实上,只有缓冲区已满或者调用了BIO_flush函数时,数据才会写入下面的BIO,所以,当任何存储在缓冲区的数据需要写入的时候(如在使用BIO_pop函数从BIO链中删除一个buffer类型BIO之前),必须使用BIO_flush函数,如果BIO链的末尾是一个非阻塞型的BIO,有时候调用BIO_flush可能出现失败,需要重试的情况。从该类型BIO读取数据时,数据从下一个BIO填充到该BIO的内部缓冲区中,然后再读出来。该类型BIO支持BIO_getsBIO_puts方法,事实上,BIO_gets函数是通过在下一个BIOBIO_read函数来实现的,所以,如果一个BIO不支持BIO_gets方法(如SSL类型的BIO),可以通过预先附加一个buffer类型BIO来实现BIO_gets的功能。

BIO_reset被调用的时候,该类型BIO里面的所有数据都会被清空。

20.2 BIO_get_buffer_num_lines

返回缓冲区中目前数据的的行数。

20.3 BIO_set_read_buffer_sizeBIO_set_write_buffer_sizeBIO_set_buffer_size

这三个函数分别设置缓冲类型BIO的读、写或者读写缓冲区的大小。初始的缓冲区大小由宏定义DEFAULT_BUFFER_SIZE决定,默认的是1024。如果设置的缓冲区大小小于DEFAULT_BUFFER_SIZE,那么就会被忽略,也就是说缓冲区大小会保持为DEFAULT_BUFFER_SIZE所定义的大小。当重新设置缓冲区大小时,里面的数据会全部被清空。成功执行返回1,否则返回0

20.4 BIO_set_buffer_read_data

该函数清空缓冲区原有的数据,并使用numbuf中的数据填充该缓冲区,如果num的大小大于目前的缓冲区设定大小,那么缓冲区就会自动扩大。成功设置返回1,否则返回0

21.Base64类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_base64(void);

21.1 BIO_f_base64

该函数返回一个Base64类型的BIO_METHOD结构,该结构定义如下(evp\bio_b64.c):

static BIO_METHOD methods_b64=

{

BIO_TYPE_BASE64,

"base64 encoding",

b64_write,

b64_read,

NULL, /* b64_puts, */

NULL, /* b64_gets, */

b64_ctrl,

b64_new,

b64_free,

b64_callback_ctrl,

};

应该注意的是,该类型的BIO其定义并不在bio目录下,而是在evp目录下。

当往该BIO写入数据时,数据被Base64编码,当从该BIO读数据时,数据被Base64解码。该BIO不支持BIO_getsBIO_puts的功能。

BIO_flush在该类型BIO被调用的时候,表示需要写入的数据已经写完,用来把最后的一段数据写入到BIO里面去。

21.2 BIO_set_flags

该函数可以用来设置标记BIO_FLAGS_BASE64_NO_NL,该标记设置后,将把所有数据编码成为一行或者说期望所有数据都在一行上。需要注意的是,由于base64编码本身格式的原因,不能准确可靠的决定编码后的数据块的结束位置,大家使用的时候自己需要注意数据的长度问题。

例子:

1、下面的程序将字符串"Hello World\n"进行base64编码并写入到标准输出设备。

BIO *bio, *b64;

char message[] = "Hello World \n";

 

b64 = BIO_new(BIO_f_base64());

bio = BIO_new_fp(stdout, BIO_NOCLOSE);

bio = BIO_push(b64, bio);

BIO_write(bio, message, strlen(message));

BIO_flush(bio);

 

BIO_free_all(bio);

2、下面的程序将base64编码的数据从标准输入设备读出并将解码数据输出到标准输出设备:

BIO *bio, *b64, bio_out;

char inbuf[512];

int inlen;

char message[] = "Hello World \n";

 

b64 = BIO_new(BIO_f_base64());

bio = BIO_new_fp(stdin, BIO_NOCLOSE);

bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);

bio = BIO_push(b64, bio);

while((inlen = BIO_read(bio, inbuf, strlen(message))) > 0)

BIO_write(bio_out, inbuf, inlen);

BIO_free_all(bio);

22.Cipher类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_cipher(void);

void BIO_set_cipher(BIO *b,const EVP_CIPHER *cipher,unsigned char *key, unsigned char *iv, int enc);

int BIO_get_cipher_status(BIO *b)

int BIO_get_cipher_ctx(BIO *b, EVP_CIPHER_CTX **pctx)

22.1 BIO_f_cipher

该函数返回cipher类型的BIO_METHOD结构,其结构定义如下(evp\bio_enc.c):

static BIO_METHOD methods_enc=

{

BIO_TYPE_CIPHER,"cipher",

enc_write,

enc_read,

NULL, /* enc_puts, */

NULL, /* enc_gets, */

enc_ctrl,

enc_new,

enc_free,

enc_callback_ctrl,

};

该类型的BIO将写入该BIO的数据加密,从该BIO读数据时数据被解密,它事实上封装了EVP_CipherInitEVP_CipherUpdateEVP_CipherFinal三种方法。它不支持BIO_putsBIO_gets的方法,如果要使用这两个方法,可以通过在前面附加一个buffer类型的BIO来实现,这在前面我们介绍过。

base64BIO相似,当调用BIO_flush函数时,表明所有数据都已经通过该类型BIO加密了,用来将最后的一段数据通过该BIO进行加密。在进行加密的时候,必须调用BIO_flush函数来把最后的数据通过BIO进行加密,否则最后的数据会在解密的时候出现失败的情况。当从一个加密类型的BIO读取数据时,当读到最后一段数据时,会通过检测EOF自动检测到数据结束标志并自动将这段数据解密。

22.2 BIO_set_cipher

该函数设置该BIO的加密算法,数据使用参数key为加密密钥,参数iv作为加密的IV(初始化向量)。如果enc设置为1,则为加密,enc设置为0,则为解密。该函数不返回值。

22.3 BIO_get_cipher_status

该函数是一个BIO_ctrl的宏,用来检测解密是否成功执行。因为在解密的时候(执行读操作的时候),如果最后一段数据发生错误,会返回0,而遇到EOF成功完成操作后也会返回0,所以必须调用本函数确定解密操作是否成功执行了。解密成功返回1,否则返回0

22.4 BIO_get_cipher_ctx

该函数也是BIO_ctrl的一个宏定义函数,它返回BIO的内部加密体制。返回的加密体制可以使用标准的加密规则进行设置。这在BIO_set_cipher函数的灵活性不能适应应用程序的需要的时候是很有用的。该函数总是返回1

23.MD类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_md(void);

int BIO_set_md(BIO *b,EVP_MD *md);

int BIO_get_md(BIO *b,EVP_MD **mdp);

int BIO_get_md_ctx(BIO *b,EVP_MD_CTX **mdcp);

Cipher类型一样,该类型的一些定义和实现文件是在evp\bio_md.c里面,而不是在bio目录下。大家要看源文件,请参看这个文件。

23.1 BIO_f_md

该函数返回一个MD类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_md=

{

BIO_TYPE_MD,"message digest",

md_write,

md_read,

NULL, /* md_puts, */

md_gets,

md_ctrl,

md_new,

md_free,

md_callback_ctrl,

};

MD类型BIO对通过它的任何数据都进行摘要操作(digest),事实上,该类型BIO封装了EVP_DigestInitEVP_DigestUpdateEVP_DigestFinal三个函数的功能和行为。该类型BIO是完全对称的,也就是说,不管是读数据(BIO_read)还是写数据(BIO_write),都进行相同的摘要操作。

BIO_gets函数执行的时候,如果给定的size参数足够大,可以完成摘要(digest)计算,那么就会返回摘要值。BIO_puts函数是不支持的,如果需要支持该函数,可以在前面附加一个buffer类型的BIO

BIO_reset函数重新初始化一个摘要类型的BIO,事实上,它是简单重新调用了EVP_DigestInit函数进行初始化。

注意,在从一个摘要BIO里面读取完摘要信息之后,在重新使用该BIO之前,必须调用BIO_resetBIO_set_md重新初始化该BIO才行。

23.2 BIO_set_md

该函数是一个BIO_ctrl函数的宏定义函数,它使用参数md设置给定BIO的摘要算法。该函数必须在执行读写操作之前调用,用来初始化一个摘要类型的BIO。调用成功返回1,否则返回0

23.3 BIO_get_md

该函数也是BIO_ctrl函数一个宏定义。它返回BIO摘要方法的指针到mdp参数里面。调用成功返回1,否则返回0

23.4 BIO_get_md_ctx

该函数返回摘要BIO的方法结构到mdcp参数里面。该结构可以作为参数使用在EVP_DigestFinalEVP_SignFinalEVP_VerifyFinal函数里,这增加了灵活性。因为该函数返回的结构是一个BIO内部的结构,所以对该结构的任何改变操作都会影响到相应的BIO,并且如果该BIO释放了,该结构指针也就无效了。调用成功返回1,否则返回0

例子

1、下列的例子创建一个包含SHA1MD5类型摘要BIOBIO链,并将数据"Hello World"通过它们进行摘要操作。

BIO *bio, *mdtmp;

char message[] = "Hello World";

bio = BIO_new(BIO_s_null());

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_sha1());

 

// 使用BIO_pushBIO链前面增加一个sink类型的BIO,作为BIO链开始的标志

bio = BIO_push(mdtmp, bio);

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_md5());

bio = BIO_push(mdtmp, bio);

/* 注意,现在mdtmp变量已经没有用了*/

BIO_write(bio, message, strlen(message));//因为最后一个BIOnull型的BIO,所以数据实际上已经自动被丢弃了。

2、下面的例子演示了从摘要类型BIO读数据的过程:

BIO *bio, *mdtmp;

char buf[1024];

int rdlen;

bio = BIO_new_file(file, "rb");

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_sha1());

bio = BIO_push(mdtmp, bio);

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_md5());

bio = BIO_push(mdtmp, bio);

do

{

rdlen = BIO_read(bio, buf, sizeof(buf));

/* 可以在这里面加入处理数据的代码 */

} while(rdlen > 0);

3、下面的例子从一个BIO链中读取摘要数据并输出。可以跟上面的例子一起使用。

BIO *mdtmp;

unsigned char mdbuf[EVP_MAX_MD_SIZE];

int mdlen;

int i;

mdtmp = bio; /* 这里假设BIO已经设置好了*/

do

{

EVP_MD *md;

mdtmp = BIO_find_type(mdtmp, BIO_TYPE_MD);

if(!mdtmp) break;

BIO_get_md(mdtmp, &md);

printf("%s digest", OBJ_nid2sn(EVP_MD_type(md)));

mdlen = BIO_gets(mdtmp, mdbuf, EVP_MAX_MD_SIZE);

for(i = 0; i < mdlen; i++) printf(":%02X", mdbuf[i]);

printf("\n");

mdtmp = BIO_next(mdtmp);

} while(mdtmp);

BIO_free_all(bio);

24.SSL类型的BIO

从名字就可以看出,这是一个非常重要的BIO类型,它封装了openssl里面的ssl规则和函数,相当于提供了一个使用SSL很好的有效工具,一个很好的助手。其定义(openssl\bio.h,openssl\ssl.h)如下:

BIO_METHOD *BIO_f_ssl(void);

#define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)ssl)

#define BIO_get_ssl(b,sslp) BIO_ctrl(b,BIO_C_GET_SSL,0,(char *)sslp)

#define BIO_set_ssl_mode(b,client) BIO_ctrl(b,BIO_C_SSL_MODE,client,NULL)

#define BIO_set_ssl_renegotiate_bytes(b,num) BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_BYTES,num,NULL);

#define BIO_set_ssl_renegotiate_timeout(b,seconds) BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL);

#define BIO_get_num_renegotiates(b) BIO_ctrl(b,BIO_C_SET_SSL_NUM_RENEGOTIATES,0,NULL);

BIO *BIO_new_ssl(SSL_CTX *ctx,int client);

BIO *BIO_new_ssl_connect(SSL_CTX *ctx);

BIO *BIO_new_buffer_ssl_connect(SSL_CTX *ctx);

int BIO_ssl_copy_session_id(BIO *to,BIO *from);

void BIO_ssl_shutdown(BIO *bio);

#define BIO_do_handshake(b) BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL)

该类型BIO的实现文件在ssl\bio_ssl.c里面,大家可以参看这个文件得到详细的函数实现信息。

24.1 BIO_f_ssl

该函数返回一个SSL类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_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_readBIO_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函数释放。

24.2 BIO_set_ssl

该函数设置SSL类型BIO的内部ssl指针指向ssl,同时使用参数c设置了关闭标志。

24.3 BIO_get_ssl

该函数返回SSL类型BIO的内部的SSL结构指针,得到该指针后,可以使用标志的SSL函数对它进行操作。

24.4 BIO_set_ssl_mode

该函数设置SSL的工作模式,如果参数client1,那么SSL工作模式为客户端模式,如果client0,那么SSL工作模式为服务器模式。

24.5 BIO_set_ssl_renegotiate_bytes

该函数设置需要重新进行session协商的读写数据的长度为num。当设置完成后,在没读写的数据一共到达num字节后,SSL连接就会自动重新进行session协商,这可以加强SSL连接的安全性。参数num最少为512字节。

24.6 BIO_set_ssl_renegotiate_timeout

该函数跟上述函数一样都是为了加强SSL连接的安全性的。不同的是,该函数采用的参数是时间。该函数设置重新进行session协商的时间,其单位是秒。当SSL session连接建立的时间到达其设置的时间时,连接就会自动重新进行session协商。

24.7 BIO_get_num_renegotiates

该函数返回SSL连接在因为字节限制或时间限制导致session重新协商之前总共读写的数据长度。

24.8 BIO_new_ssl

该函数使用ctx参数所代表的SSL_CTX结构创建一个SSL类型的BIO,如果参数client为非零值,就使用客户端模式。

24.9 BIO_new_ssl_connect

该函数创建一个包含SSL类型BIO的新BIO链,并在后面附加了一个连接类型的BIO。方便而且有趣的是,因为在filter类型的BIO里,如果是该BIO不知道(没有实现)BIO_ctrl操作,它会自动把该操作传到下一个BIO进行调用,所以我们可以在调用本函数得到BIO上直接调用BIO_set_host函数来设置服务器名字和端口,而不需要先找到连接BIO

24.10 BIO_new_buffer_ssl_connect

创建一个包含buffer型的BIO,一个SSL类型的BIO以及一个连接类型的BIO

24.11 BIO_ssl_copy_session_id

该函数将BIOfromSSL Session ID拷贝到BIOto中。事实上,它是通过查找到两个BIO链中的SSL类型BIO,然后调用SSL_copy_session_id来完成操作的。

24.12 BIO_ssl_shutdown

该函数关闭一个BIO链中的SSL连接。事实上,该函数通过查找到该BIO链中的SSL类型BIO,然后调用SSL_shutdown函数关闭其内部的SSL指针。

24.13 BIO_do_handshake

该函数在相关的BIO上启动SSL握手过程并建立SSL连接。连接成功建立返回1,否则返回0或负值,如果连接BIO是非阻塞型的BIO,此时可以调用BIO_should_retry函数以决定释放需要重试。如果调用该函数的时候SSL连接已经建立了,那么该函数不会做任何事情。一般情况下,应用程序不需要直接调用本函数,除非你希望将握手过程跟其它IO操作分离开来。

需要注意的是,如果底层是阻塞型(openssl帮助文档写的是非阻塞型,non blocking,但是根据上下文意思已经BIO的其它性质,我个人认为是阻塞型,blocking才是正确的)的BIO,在一些意外的情况SSL类型BIO下也会发出意外的重试请求,如在执行BIO_read操作的时候如果启动了session重新协商的过程就会发生这种情况。在0.9.6和以后的版本,可以通过SSL的标志SSL_AUTO_RETRY将该类行为禁止,这样设置之后,使用阻塞型传输的SSL类型BIO就永远不会发出重试的请求。

例子

1.一个SSL/TLS客户端的例子,完成从一个SSL/TLS服务器返回一个页面的功能。其中IO操作的方法跟连接类型BIO里面的例子是相同的。

BIO *sbio, *out;

int len;

char tmpbuf[1024];

SSL_CTX *ctx;

SSL *ssl;

ERR_load_crypto_strings();

ERR_load_SSL_strings();

OpenSSL_add_all_algorithms();

//如果系统平台不支持自动进行随机数种子的设置,这里应该进行设置(seed PRNG)

ctx = SSL_CTX_new(SSLv23_client_method());

//通常应该在这里设置一些验证路径和模式等,因为这里没有设置,所以该例子可以跟使用任意CA签发证书的任意服务器建立连接

sbio = BIO_new_ssl_connect(ctx);

BIO_get_ssl(sbio, &ssl);

if(!ssl)

{fprintf(stderr, "Can't locate SSL pointer\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, "Error connecting to server\n");

ERR_print_errors_fp(stderr);

}

if(BIO_do_handshake(sbio) <= 0)

{

fprintf(stderr, "Error establishing SSL connection\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;

int len;

char tmpbuf[1024];

SSL_CTX *ctx;

SSL *ssl;

ERR_load_crypto_strings();

ERR_load_SSL_strings();

OpenSSL_add_all_algorithms();

//可能需要进行随机数的种子处理(seed PRNG

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, "Error setting up SSL_CTX\n");

ERR_print_errors_fp(stderr);

return 0;

}

//可以在这里设置验证路径,DHDSA算法的临时密钥回调函数等等

/* 创建一个新的服务器模式的SSL类型BIO*/

sbio=BIO_new_ssl(ctx,0);

BIO_get_ssl(sbio, &ssl);

if(!ssl)

{fprintf(stderr, "Can't locate SSL pointer\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);

/* 设置 accept BIO */

if(BIO_do_accept(acpt) <= 0)

{

fprintf(stderr, "Error setting up accept BIO\n");

ERR_print_errors_fp(stderr);

return 0;

}

/* 等待连接的建立 */

if(BIO_do_accept(acpt) <= 0)

{

fprintf(stderr, "Error in connection\n");

ERR_print_errors_fp(stderr);

return 0;

}

/*因为我们只想处理一个连接,所以可以删除和释放 accept BIO*/

sbio = BIO_pop(acpt);

BIO_free_all(acpt);

if(BIO_do_handshake(sbio) <= 0)

{

fprintf(stderr, "Error in SSL handshake\n");

ERR_print_errors_fp(stderr);

return 0;

}

BIO_puts(sbio, "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n");

BIO_puts(sbio, "<pre>\r\nConnection Established\r\nRequest headers:\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);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值