OpenSSL BIO源码简析
文章目录
- 1. BIO简介
- BIO chain
- BIO数据结构
- BIO_METHOD数据结构
- 2. Base64示例分析
- 初始化
- 构造BIO链
- 写数据
- free
1. BIO简介
相关文档
/html/man7/bio.html
/html/man3/BIO_*.html
bio - Basic I/O abstraction,即IO抽象层。
BIO有两种:
- source/sink BIO,即数据源,如socket BIO、file BIO,初始化接口以
BIO_s_
开头; - filter BIO,过滤器,用来接收和传递数据,初始化接口以
BIO_f_
开头;
BIO chain
BIO可以组成一条链,即使是单个BIO,实质也是有一个节点的链。
一个链通常由1个source/sink BIO、1个或多个filter BIO组成。
数据从第一个BIO写入或读出,并传递到最后一个节点(通常是source/sink BIO)。
相关api:
- BIO_push
- BIO_free_all 释放整个链
BIO数据结构
源码位置:\crypto\bio\bio_local.h
struct bio_st {// BIO_new初始化需要提供libctx和method参数// BIO *BIO_new(const BIO_METHOD *method)// {// return BIO_new_ex(NULL, method);// }OSSL_LIB_CTX *libctx; // NULL: default contextzconst BIO_METHOD *method;/* bio, mode, argp, argi, argl, ret */
#ifndef OPENSSL_NO_DEPRECATED_3_0BIO_callback_fn callback;
#endifBIO_callback_fn_ex callback_ex;char *cb_arg; /* first argument for the callback */// 这里用int来作标志应该有点浪费// 用bool或bit更好些int init; // 初始化标志int shutdown;int flags; /* extra storage */int retry_reason;int num;void *ptr; // BIO_set_data() // bio链本质是双向链表struct bio_st *next_bio; /* used by filter BIOs */struct bio_st *prev_bio; /* used by filter BIOs */CRYPTO_REF_COUNT references;uint64_t num_read;uint64_t num_write;CRYPTO_EX_DATA ex_data;CRYPTO_RWLOCK *lock; // 线程读写锁
};
BIO_METHOD数据结构
源码路径:\include\internal\bio.h
struct bio_method_st {int type;char *name;int (*bwrite) (BIO *, const char *, size_t, size_t *);int (*bwrite_old) (BIO *, const char *, int);int (*bread) (BIO *, char *, size_t, size_t *);int (*bread_old) (BIO *, char *, int);int (*bputs) (BIO *, const char *);int (*bgets) (BIO *, char *, int);long (*ctrl) (BIO *, int, long, void *);int (*create) (BIO *);int (*destroy) (BIO *);long (*callback_ctrl) (BIO *, int, BIO_info_cb *);
};
该结构除了类型和名称,其余均是函数指针。
2. Base64示例分析
借用官网Base64示例:https://www.openssl.org/docs/man3.0/man3/BIO_f_base64.html,它将"hello world \n"的base64输出到stdout:
#include <iostream>
#include <openssl/bio.h>
#include <openssl/evp.h>
int main()
{BIO* bio, * b64;char message[] = "Hello World \n";b64 = BIO_new(BIO_f_base64());bio = BIO_new_fp(stdout, BIO_NOCLOSE);BIO_push(b64, bio);BIO_write(b64, message, strlen(message));BIO_flush(b64);// SGVsbG8gV29ybGQgCg==BIO_free_all(b64);getchar();return 0;
}
用vscode在源码目录搜索“base64”,并没有找到实现源码,于是全局搜索编码表,定位到源码路径:
// \crypto\evp\encode.c
static const unsigned char data_bin2ascii[65] ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
把encode.c拖到vs中,在引用编码表的地方下断点,调试示例程序,成功断下:
BIO_flush
调用栈如下:
libcrypto-3.dll!evp_encodeblock_int(evp_Encode_Ctx_st * ctx, unsigned char * t, const unsigned char * f, int dlen) Line 238 C
libcrypto-3.dll!EVP_EncodeFinal(evp_Encode_Ctx_st * ctx, unsigned char * out, int * outl) Line 222 C
libcrypto-3.dll!b64_ctrl(bio_st * b, int cmd, long num, void * ptr) Line 506 C
libcrypto-3.dll!BIO_ctrl(bio_st * b, int cmd, long larg, void * parg) Line 579 C
TestOpenSSL.exe!main() Line 13 C++
EVP是OpenSSL的算法实现接口,结合evp文档(/html/man7/evp.html
),这里正是base64编解码逻辑:
The EVP_EncodeXXX and EVP_DecodeXXX functions implement base 64 encoding and decoding.
初始化
b64 = BIO_new(BIO_f_base64());
bio = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_new_fp()
,内部其实是调用了BIO_new(BIO_s_file())
初始化一个source/sink BIO
。所以只分析第一个base64 filter BIO。
BIO_f_base64()
返回一个base64的静态BIO_METHOD
:
// \crypto\evp\bio_b64.c
static const BIO_METHOD methods_b64 = {BIO_TYPE_BASE64,"base64 encoding",bwrite_conv,b64_write,bread_conv,b64_read,b64_puts,NULL, /* b64_gets, */b64_ctrl,b64_new,b64_free,b64_callback_ctrl,
};const BIO_METHOD *BIO_f_base64(void)
{return &methods_b64;
}
通过BIO_TYPE_BASE64
这个宏type,可以在\include\openssl\bio.h
定位到其它method type:
/*在源码层面,BIO其实是有3种。
*/
/* There are the classes of BIOs */
# define BIO_TYPE_DESCRIPTOR 0x0100 /* socket, fd, connect or accept */
# define BIO_TYPE_FILTER 0x0200
# define BIO_TYPE_SOURCE_SINK 0x0400/* These are the 'types' of BIOs */
# define BIO_TYPE_NONE 0
# define BIO_TYPE_MEM ( 1|BIO_TYPE_SOURCE_SINK)
# define BIO_TYPE_FILE ( 2|BIO_TYPE_SOURCE_SINK)# define BIO_TYPE_FD ( 4|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
// ...
# define BIO_TYPE_BASE64 (11|BIO_TYPE_FILTER)
BIO_new
则根据base 64 method初始化filter BIO:
method->create
为b64_new()
,用于初始化bio->ptr==b64_ctx
// \crypto\evp\bio_b64.c
typedef struct b64_struct {/** BIO *bio; moved to the BIO structure*/int buf_len;int buf_off;int tmp_len; /* used to find the start when decoding */int tmp_nl; /* If true, scan until '\n' */int encode;int start; /* have we started decoding yet? */int cont; /* <= 0 when finished */EVP_ENCODE_CTX *base64; char buf[EVP_ENCODE_LENGTH(B64_BLOCK_SIZE) + 10];char tmp[B64_BLOCK_SIZE];
} BIO_B64_CTX;
函数栈如下:
> libcrypto-3.dll!b64_new(bio_st * bi) Line 71 CBIO_B64_CTX *ctx;//...BIO_set_data(bi, ctx); // set bi->ptrBIO_set_init(bi, 1); // initialized libcrypto-3.dll!BIO_new_ex(ossl_lib_ctx_st * libctx, const bio_method_st * method) Line 104 Cif (method->create != NULL && !method->create(bio)) {// ...goto err;}if (method->create == NULL)bio->init = 1; // 若没有单独的create初始化函数,则直接设置init标志libcrypto-3.dll!BIO_new(const bio_method_st * method) Line 122 Creturn BIO_new_ex(NULL, method);TestOpenSSL.exe!main() Line 9 C++
构造BIO链
源码位置:\crypto\bio\bio_lib.c
// BIO_push(b64, bio);
BIO *BIO_push(BIO *b, BIO *bio)
{BIO *lb;if (b == NULL)return bio;lb = b;while (lb->next_bio != NULL)lb = lb->next_bio;lb->next_bio = bio;if (bio != NULL)bio->prev_bio = lb;/* called to do internal processing */BIO_ctrl(b, BIO_CTRL_PUSH, 0, lb);return b;
}
在BIO链表中,stdout source/sink BIO是在 base64 filter BIO之后的。
写数据
BIO_write(b64, message, strlen(message));
BIO_flush(b64);
再看一下methods_b64:
static const BIO_METHOD methods_b64 = {BIO_TYPE_BASE64,"base64 encoding",bwrite_conv, // int (*bwrite) (BIO *, const char *, size_t, size_t *);b64_write, // int (*bwrite_old) (BIO *, const char *, int);//...
}
调用BIO_write,其实是调用base64 method的bwrite
和bwrite_old
函数,形成如下函数栈:
libcrypto-3.dll!EVP_EncodeInit(evp_Encode_Ctx_st * ctx) Line 156 C
libcrypto-3.dll!b64_write(bio_st * b, const char * in, int inl) Line 346 C
libcrypto-3.dll!bwrite_conv(bio_st * bio, const char * data, unsigned int datal, unsigned int * written) Line 77 Cret = bio->method->bwrite_old(bio, data, (int)datal);
libcrypto-3.dll!bio_write_intern(bio_st * b, const void * data, unsigned int dlen, unsigned int * written) Line 362 Cret = b->method->bwrite(b, data, dlen, &local_written);
libcrypto-3.dll!BIO_write(bio_st * b, const void * data, int dlen) Line 384 C
TestOpenSSL.exe!main() Line 12 C++
但经过调试,执行BIO_write后
标准输出(就是屏幕)并没有回显,在evp_encodeblock_int
的断点也没有断下。而根据最开始贴出的BIO_flush
调用栈,它在执行缓冲区刷新时才开始执行evp_encodeblock_int
,文档如下:
https://www.openssl.org/docs/man3.0/man3/BIO_flush.html
BIO_flush() normally writes out any internally buffered data, in some cases it is used to signal EOF and that no more data will be written.
该刷新函数底层是调用BIO_ctrl()
实现的,该类控制函数通常并不直接使用,而是通过BIO_flush
等定义于\include\openssl\bio.h
中的宏函数来调用的。
执行evp_encodeblock_int
后,b64_ctrl()
会将base64写入bio链下一节点,并执行下一节点的ctrl函数:
libcrypto-3.dll!b64_write(bio_st * b, const char * in, int inl) Line 354 Cnext = BIO_next(b);// ...while (n > 0) {i = BIO_write(next, &(ctx->buf[ctx->buf_off]), n); // 写入stdout bio 这时屏幕会出现base64值// ...}
libcrypto-3.dll!b64_ctrl(bio_st * b, int cmd, long num, void * ptr) Line 490 Ccase BIO_CTRL_FLUSH:/* do a final write */while (ctx->buf_len != ctx->buf_off) {i = b64_write(b, NULL, 0); // <--// ...}// .../* Finally flush the underlying BIO */ret = BIO_ctrl(next, cmd, num, ptr); // stdout bio的ctrl函数为file_ctrl()
libcrypto-3.dll!BIO_ctrl(bio_st * b, int cmd, long larg, void * parg) Line 579 Cret = b->method->ctrl(b, cmd, larg, parg);
TestOpenSSL.exe!main() Line 13 C++
free
BIO_free_all(b64);
这个函数的作用,猜也能猜的到,遍历链表逐个释放空间。
// \crypto\bio\bio_lib.c
void BIO_free_all(BIO *bio)
{BIO *b;int ref;while (bio != NULL) {b = bio; // 从第一个节点开始释放ref = b->references;bio = bio->next_bio;BIO_free(b);/* Since ref count > 1, don't free anyone else.意思是别人还在用 别删*/if (ref > 1)break;}
}// 需要释放的东西还是很多的
int BIO_free(BIO *a)
{int ret;if (a == NULL)return 0;// 引用数减1if (CRYPTO_DOWN_REF(&a->references, &ret, a->lock) <= 0)return 0;REF_PRINT_COUNT("BIO", a);if (ret > 0)return 1;REF_ASSERT_ISNT(ret < 0);if (HAS_CALLBACK(a)) {ret = (int)bio_call_callback(a, BIO_CB_FREE, NULL, 0, 0, 0L, 1L, NULL);if (ret <= 0)return 0;}if ((a->method != NULL) && (a->method->destroy != NULL))a->method->destroy(a); // b64_free()CRYPTO_free_ex_data(CRYPTO_EX_INDEX_BIO, a, &a->ex_data);CRYPTO_THREAD_lock_free(a->lock);OPENSSL_free(a);return 1;
}
OpenSSL BIO源码简析相关推荐
- 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析
目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...
- django源码简析——后台程序入口
django源码简析--后台程序入口 这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主 ...
- (Ajax)axios源码简析(三)——请求与取消请求
传送门: axios源码简析(一)--axios入口文件 axios源码简析(二)--Axios类与拦截器 axios源码简析(三)--请求与取消请求 请求过程 在Axios.prototype.re ...
- java ArrayList 概述 与源码简析
ArrayList 概述 与源码简析 1 ArrayList 创建 ArrayList<String> list = new ArrayList<>(); //构造一个初始容量 ...
- Spring Boot源码简析 @EnableTransactionManagement
相关阅读 Spring Boot源码简析 事务管理 Spring Boot源码简析 @EnableAspectJAutoProxy Spring Boot源码简析 @EnableAsync Sprin ...
- ffmpeg实战教程(十三)iJKPlayer源码简析
要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...
- 【Android项目】本地FM收音机开发及源码简析
[Android项目]本地FM收音机开发及源码简析 目录 1.概述 2.收音机的基本原理 3.收音机其他信息 RDS功能 4.Android开发FM收音机源码解析 5.App层如何设计本地FM应用 6 ...
- Log-Pilot 源码简析
Log-Pilot 源码简析 简单介绍 源码简析 Pilot结构体 Piloter接口 main函数 Pilot.Run Pilot.New Pilot.watch Pilot.processEven ...
- Spring Boot源码简析 @Qualifier
源码 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementT ...
最新文章
- md5加密用户登陆遇到的问题及解决办法
- html实现全屏效果原理,HTML5 实现全屏效果
- ios中常用数据类型相互转换
- html字体整体偏移,html/css:在悬停鼠标时增加字体大小的链接时的位置偏移
- 活动目录实战系列七(降级主DC为成员服务器)
- 程序员面试系列之Java单例模式的攻击与防御
- 不止面部识别,一切关于人脸AI的资源都能在这里下载
- 探索未来|一文看懂小米年度技术峰会 · 软件技术专场
- 蚁群算法(Ant Colony Optimization)
- 基于信创运维平台,实现国产化网络自动巡检
- What?Tomcat-竟然也算中间件?
- 体验最近火爆的ChatGPT,真的被震惊到了
- Python爬虫实例(3)--BeautifulSoup的CSS选择器
- 高德地图Demo,生成apk发布到手机签名不一致
- 【转】 教你一眼认出英语单词的意思
- 使用python进行web抓取
- mac xcode 开发C++
- 杜亚楠: 用微组织技术撬动细胞培养大变革
- python 三大器
- Windows Update禁用后自动开启的解决办法