BIO是OpenSSL的一个紧张的布局和概念,是对数据IO与传递处理的一种范例抽象和功能封装,这里所说的数据IO与传递的“介质”包罗:内存、文件、日记、标准装备、网络socket等,“处理”包罗简朴、加/解密、摘要、ssl协议下的读/写等的数据变更。
本文重要先容了BIO的布局和使用方法,并以示例方式给出了一种对BIO的封装,可为下一步与socket的干系操纵联合,编写自定义的“加密/解密流”。
本文示例实用于openssl3.0+。
1.概念
BIO是针对数据传递而设计的逻辑布局,因此它基本的概念模型是“管道”:数据由BIO一端进入,另一端流出,中间也可能举行数据变更。这样的“管道”布局可以依据是否有“缓存”,出入口等条件举行划分。
OpenSSL中将BIO分为两种范例:
- Filter BIO:就是纯管道型BIO,数据不能保存在此中。
- source/sink BIO:自带有容器的BIO,数据举行缓存,如果进一步细分,source BIO就是只出不进BIO,sink BIO就是只进不出BIO。
两种范例的BIO的概念示意图如上图所示。可以想象,如果将BIO首尾连接起来,就会构成BIO链,也如上图所示。BIO链在使用上,固然仅对首尾BIO举行了读写操纵,但是这种操纵是会依次传递给下一个BIO的,因而BIO链逻辑上看作是一个复杂的BIO。
2.范例
OpenSSL中已经预定义了若干种BIO,直接可以使用它们。
重要Source/Sink范例BIO
- BIO_s_file/ BIO_s_fd:文件BIO,BIO_s_file对应FILE*,而BIO_s_fd对应POSIX文件形貌符,它们可用于写入和读取文件。
- BIO_s_socket:网络socketBIO,用于通过网络举行通信。
- BIO_s_null: 空BIO,类似/dev/null,只能写入,读取数据会导致EOF。
- BIO_s_mem:内存BIO,用于写入和读取内存。
- BIO_s_bio:一种特殊的BIO,被称为BIO pair,后文单独说明。
重要Filter范例BIO
- BIO_f_base64:base64 BIO,通过此BIO的BIO_write将数据编码为base64格式, BIO_read通过此BIO解码base64格式的数据。
- BIO_f_cipher:暗码BIO,通过它的数据会被加/解密,暗码算法可以设置。
- BIO_f_md:摘要计算BIO,它不会修改通过它的数据,而仅计算流经此中的数据摘要,摘要算法可以设置,使用特殊功能检索计算出的摘要。
- BIO_f_buffer:缓冲BIO,它也不会更改通过它的数据。写入此BIO的数据被缓冲,因此并非每次对该BIO的写入操纵都会导致将数据写入下一个BIO。至于阅读,情况类似。这样可以减少位于缓冲IO后面的BIO上的IO操纵数。
- BIO_f_ssl :SSL/TLS 协议BIO,通过它的数据会按照协议规则举行加解密
- 3.基本使用函数
创建/释放函数为:
- BIO *BIO_new(const BIO_METHOD *)
- BIO_free_all(BIO *)
复制代码 设置/控制基本函数为:
- BIO_ctrl(BIO *,int,long ,void *)
复制代码 以此函数为基础,定义了一些方便使用的宏:BIO_reset,BIO_tell,BIO_eof,BIO_flush等
读写操纵的基本函数为:
- int BIO_read_ex(BIO *, void *, size_t, size_t *)
- int BIO_write_ex(BIO *, const void *, size_t, size_t *)
复制代码 对于BIO链,bio_st布局中有变量next_bio,prev_bio,可以指向其前后的BIO,这也是BIO链式操纵的基础。BIO链的基本操纵函数为:
- BIO * BIO_push(BIO *a,BIO *b);
- BIO * BIO_pop(BIO *b);
复制代码 前者将b链接到a之后,返回b,后者将b从链条上摘除,返回b,原来的链条依然完整。
BIO还有一些辅助函数,例如处理错误,获取状态等函数。
4.BIO Pair
BIO对是一种比较特殊的BIO,它由两个BIO构成,但从它的实当代码来看,它似乎是与BIO平行的一种实现方式,因而不能单纯的用BIO链来说明,它的逻辑布局如下图所示。
BIO pair连接两个外部端A,B,从外部来看,A端写,则可从B端读出;B端写,则可从A端读出。从内部来看,有两个内存型BIOA和BIOB,分别与A端和B端相连,它们有各自的缓存,A端存入和B端读取的数据,使用的是BIOA缓存,B端写入A端读取则使用的是BIOB的缓存。
因此BIO pair类似于“双向有缓存管道”,从任一端写入,另一端读出,由于内存缓存的使用,使得一端的读/写操纵都是“即刻”完成的,它不用关心另一端什么时候做写/读操纵,这就是典范的异步操纵。目前很多网络通信库采用的都是异步读写,因而BIO pair这种应用模型是OpenSSL适配这些网络库的一个紧张方法。
创建BIO pair的简便方法是BIO_new_bio_pair,它实际上是 BIO_new, BIO_make_bio_pair, BIO_set_write_buf_size的组合。
5.构造自定义BIO范例
OpenSSL已定义了若干BIO,当然也可以自定义一个。下面的示例构造了一个简朴的CMYBIO,以此来说明BIO的工作原理。
- class CMYBIO
- {
- private:
- static int write_ex(BIO* h, const char* buf, size_t num, size_t * len)
- {
- printf("CMYBIO::write_ex\n");
- for (int i = 0; i < num; ++i) printf("%c", buf[i]);
- printf("\n");
- *len = num;
- return 1;
- }
- static int read_ex(BIO* h, char* buf, size_t size, size_t* len)
- {
- printf("CMYBIO::read_ex\n");
- unsigned int* opt = (unsigned int*)BIO_get_data(h);
- if (*opt == 0)
- {
- size = size > 6 ? 6 : size;
- memcpy(buf, "openss", size);
- *len = size;
- // BIO_clear_retry_flags(h);
- // BIO_copy_next_retry(h);
- return 1;
- }
- if (*opt == 1)
- {
- size = 9;
- memcpy(buf, "MTIzNDU2\n", size); //base64("123456")
- *len = size;
- BIO_clear_retry_flags(h);
- // BIO_copy_next_retry(h);
- return 1;
- }
- return 0;
- }
- static long ctrl(BIO* h, int cmd, long arg1, void* arg2)
- {
- printf("CMYBIO::ctrl[%d]\n", cmd);
- if (cmd == 0xff)
- {
- unsigned int* opt = (unsigned int*)BIO_get_data(h);
- *opt = arg1;
- }
- return 1;
- }
- static int create(BIO* bio)
- {
- printf("CMYBIO::create\n");
- BIO_set_init(bio, 1); //”init” must be set explicitly,otherwise “read”/”write” methods will be invoked. Although it seems odd, the “framework” would not do It for you by the returned value.
- unsigned int* opt =(unsigned int*) malloc(sizeof(int));
- *opt = 0;
- BIO_set_data(bio,opt);
- return 1;
- }
- static int destory(BIO* bio)
- {
- printf("CMYBIO::destory\n");
- unsigned int* opt=(unsigned int*)BIO_get_data(bio);
- free(opt);
- return 1;
- }
- static BIO_METHOD* method;
- public:
- static BIO_METHOD* BIO_s_my()
- {
- return CMYBIO::method;
- }
- static void UnInit()
- {
- BIO_meth_free(CMYBIO::method);
- }
- static void Init(void)
- {
- CMYBIO::method = BIO_meth_new((100 | BIO_TYPE_SOURCE_SINK), "My BIO");
- int r;
- r = BIO_meth_set_create(CMYBIO::method, CMYBIO::create);
- r = BIO_meth_set_destroy(CMYBIO::method, CMYBIO::destory);
- r = BIO_meth_set_write_ex(CMYBIO::method, CMYBIO::write_ex);
- r=BIO_meth_set_read_ex(CMYBIO::method, CMYBIO::read_ex);
- r=BIO_meth_set_puts(CMYBIO::method, nullptr);
- r=BIO_meth_set_gets(CMYBIO::method, nullptr);
- r=BIO_meth_set_ctrl(CMYBIO::method, CMYBIO::ctrl);
- r=BIO_meth_set_callback_ctrl(CMYBIO::method, nullptr);
- }
- };
- BIO_METHOD* CMYBIO::method=nullptr;
复制代码 下面的代码使用了上面构造的CMYBIO,此中第一段是单独使用CMYBIO,第二段是与BIO_f_base64构成链式应用。CMYBIO作为单独应用,构造的功能已经够用,但构成链式应用,由于链式调用必要在每个具体的实现方法内来完成,因此上面的代码还不敷,为简化,这里仅把CMYBIO作为链式应用的末了一级来使用。
- long bio_cb(BIO* b, int oper, const char* argp, size_t len, int argi, long argl, int ret, size_t* processed)
- {
- printf("bio callback:%d, %u\n", oper,(unsigned int)len);
- return 1;
- }
- void test_bio()
- {
- BIO* bmy = NULL;
- size_t s = 0;
- int len = 0;
- char* out = NULL;
- char cc[24];
- memset(cc, 0, sizeof(cc));
- CMYBIO::Init();
- bmy = BIO_new(CMYBIO::BIO_s_my());
- BIO_set_callback_ex(bmy, bio_cb);
- len = BIO_write_ex(bmy, "openssl", 7, &s);
- printf("BIO_write_ex return [%d, %u]\n\n", len, (unsigned int)s);
- len = 7;
- out = (char*)OPENSSL_malloc(len);
- memset(out, 0, len);
- len = BIO_read_ex(bmy, out, len-1, &s);
- printf("BIO_read_ex return [%d,%u]%s\n", len, (unsigned int)s, out);
- OPENSSL_free(out);
- BIO_free(bmy);
- printf("--------------------------------\n");
- BIO* b64 = BIO_new(BIO_f_base64());
- BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
- bmy = BIO_new(CMYBIO::BIO_s_my());
- BIO_push(b64, bmy);
- len=BIO_write_ex(b64, "123456", 6, &s);
- printf("BIO_write_ex return [%d, %u]\n\n", len, (unsigned int)s);
- BIO_flush(b64); //important!!!
- BIO_ctrl(bmy, 0xff, 1, nullptr);
- len = BIO_read_ex(b64, cc, sizeof(cc)- 1, &s);
- printf("BIO_read_ex return [%d,%u]%s\n\n", len, (unsigned int)s, cc);
- //OPENSSL_free(out);
- BIO_free_all(b64);
- CMYBIO::UnInit();
- }
复制代码 整个过程输出如下图所示。前一个示例重要展示CMYBIO中各方法的调用过程,此中还设置了“钩子”函数,从输出可以清晰看出BIO的工作过程。后一个示例稍有些贫苦,此中增长了CMYBIO内部的设置,从而可以实现不同的效果。
必要注意的是,BIO_f_base64有其特殊之处,对于“write”,必要在完成后调用flush才能精确工作,对于“read”,编码串结束的标志是“\n”,但还是要设置BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL),否则可能会有错误。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |